From: Steven Niu <steven.niu.uj@renesas.com> To: alexandre.belloni@bootlin.com, linux-i3c@lists.infradead.org, linux-kernel@vger.kernel.org, robh+dt@kernel.org, krzysztof.kozlowski+dt@linaro.org, conor+dt@kernel.org, devicetree@vger.kernel.org Cc: zbigniew.lukwinski@linux.intel.com, Steven Niu <steven.niu.uj@renesas.com> Subject: [PATCH 2/2] i3c: i3c-hub: Add Renesas RG3MxxB12A1 I3C HUB driver Date: Sat, 17 Feb 2024 21:14:12 +0800 [thread overview] Message-ID: <20240217131412.4145506-2-steven.niu.uj@renesas.com> (raw) In-Reply-To: <20240217131412.4145506-1-steven.niu.uj@renesas.com> RG3MxxB12A1 I3C HUB is smart device which provide multiple functionality: * Enabling voltage compatibility across I3C Controller and Target Device. * Enabling voltage compatibility across I3C Controller and Target devices. * Bus capacitance isolation. * Address conflict isolation. * I3C port expansion. * Two controllers in a single I3C bus. * I3C and SMBus device compatibility. * GPIO expansion. Signed-off-by: Steven Niu <steven.niu.uj@renesas.com> --- drivers/i3c/Kconfig | 12 + drivers/i3c/Makefile | 1 + drivers/i3c/i3c-hub.c | 1982 ++++++++++++++++++++++++++++++++++++ drivers/i3c/master.c | 19 +- include/linux/i3c/device.h | 2 + 5 files changed, 2012 insertions(+), 4 deletions(-) create mode 100644 drivers/i3c/i3c-hub.c diff --git a/drivers/i3c/Kconfig b/drivers/i3c/Kconfig index 30a441506f61..6b6a1f29bd33 100644 --- a/drivers/i3c/Kconfig +++ b/drivers/i3c/Kconfig @@ -21,4 +21,16 @@ menuconfig I3C if I3C 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. + endif # I3C diff --git a/drivers/i3c/Makefile b/drivers/i3c/Makefile index 11982efbc6d9..ca298faaee9a 100644 --- a/drivers/i3c/Makefile +++ b/drivers/i3c/Makefile @@ -2,3 +2,4 @@ i3c-y := device.o master.o obj-$(CONFIG_I3C) += i3c.o obj-$(CONFIG_I3C) += master/ +obj-$(CONFIG_I3C_HUB) += i3c-hub.o diff --git a/drivers/i3c/i3c-hub.c b/drivers/i3c/i3c-hub.c new file mode 100644 index 000000000000..73a9b96e6635 --- /dev/null +++ b/drivers/i3c/i3c-hub.c @@ -0,0 +1,1982 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2021 - 2023 Intel Corporation.*/ + +#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> + +#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 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 + +/* 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_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 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 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 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 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 + +/* 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 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 + +/* 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 */ +#define I3C_HUB_POLLING_ROLL_PERIOD_MS 10 + +/* 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 + +struct tp_setting { + u8 mode; + u8 pullup_en; + bool always_enable; +}; + +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]; +}; + +struct smbus_backend { + struct i2c_client *client; + const char *compatible; + int addr; + struct list_head list; +}; + +struct i2c_adapter_group { + u8 tp_mask; + u8 tp_port; + u8 used; + + struct delayed_work delayed_work_polling; + struct list_head backend_entry; + u8 polling_last_status; +}; + +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 i2c_adapter_group smbus_port_adapter; + struct device_node *of_node; + struct i3c_hub *priv; +}; + +struct i3c_hub { + struct i3c_device *i3cdev; + struct i3c_master_controller *driving_master; + struct regmap *regmap; + 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_LOGICAL_BUS_MAX_COUNT]; + + /* Offset for reading HUB's register. */ + u8 reg_addr; + struct dentry *debug_dir; +}; + +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 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 }, +}; + +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) { + if (ret != -EINVAL) /* Lack of property is not considered as a problem. */ + 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 void i3c_hub_tp_of_get_setting(struct device *dev, + const struct device_node *node, + struct tp_setting tp_setting[]) +{ + struct device_node *tp_node; + u32 id; + + for_each_available_child_of_node(node, tp_node) { + if (!tp_node->name || of_node_cmp(tp_node->name, "target-port")) + continue; + + if (!tp_node->full_name || + (sscanf(tp_node->full_name, "target-port@%u", &id) != 1)) { + dev_warn(dev, "Invalid target port node found in DT: %s\n", + tp_node->full_name); + continue; + } + + if (id >= I3C_HUB_TP_MAX_COUNT) { + dev_warn(dev, "Invalid target port index found in DT: %i\n", id); + continue; + } + 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); + tp_setting[id].always_enable = + of_property_read_bool(tp_node, "always-enable"); + } +} + +static void i3c_hub_of_get_conf_static(struct device *dev, + const struct device_node *node) +{ + struct i3c_hub *priv = dev_get_drvdata(dev); + + 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); + + i3c_hub_tp_of_get_setting(dev, node, priv->settings.tp); +} + +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; + + 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; + } +} + +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; + int ret; + int i; + + /* TBD: Read type of HUB from register I3C_HUB_DEV_INFO_0 to learn target ports count. */ + for (i = 0; i < I3C_HUB_TP_MAX_COUNT; ++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); + + 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); + } + 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); + } + } + + ret = regmap_update_bits(priv->regmap, I3C_HUB_TP_IO_MODE_CONF, smbus_mask, smbus_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_update_bits(priv->regmap, I3C_HUB_TP_NET_CON_CONF, i3c_mask, i3c_val); +} + +static int i3c_hub_configure_hw(struct device *dev) +{ + int ret; + + 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; + + return i3c_hub_hw_configure_tp(dev); +} + +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 < I3C_HUB_LOGICAL_BUS_MAX_COUNT) { + 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; + 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 << 4 | (u32)priv->hub_dt_sel_id; + pin_id = (u32)priv->hub_pin_cp1_id << 4 | (u32)priv->hub_pin_sel_id; + + if ((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; + + 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); + + for (i = 0; i < I3C_HUB_TP_MAX_COUNT; ++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); + } + + 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_status, u8 *status) +{ + unsigned long time_to_timeout = 0; + unsigned int status_read; + ktime_t start, end; + int ret; + + start = ktime_get_real(); + + while (time_to_timeout < (long)I3C_HUB_SMBUS_400kHz_TIMEOUT) { + ret = regmap_read(priv->regmap, target_port_status, &status_read); + if (ret) + return ret; + + *status = (u8)status_read; + + if ((*status & I3C_HUB_TP_BUFFER_STATUS_MASK) == I3C_HUB_XFER_SUCCESS) + return 0; + + if (!(*status & I3C_HUB_TP_BUFFER_STATUS_MASK) && + (*status & I3C_HUB_TP_TRANSACTION_CODE_MASK)) { + dev_err(&priv->i3cdev->dev, "Invalid transfer status returned\n"); + return 0; + } + + end = ktime_get_real(); + time_to_timeout = end - start; + } + dev_err(&priv->i3cdev->dev, "Status read timeout reached\n"); + return 0; +} + +/* + * i3c_hub_smbus_msg() - This starts a smbus write 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. + * @priv: a pointer to the i3c hub main structure + * @ssport: 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 + * @rw: number informing if the message is of read or write type (0 for write, 1 for read) + * @return_status: number passed by reference where the return status code is saved + * + * Return: on success function returns zero. Otherwise the regmap read or write error code + * is returned + */ +static int i3c_hub_smbus_msg(struct i3c_hub *priv, + struct i2c_msg *xfers, + u8 target_port, + u8 nxfers_i, u8 rw, u8 *return_status) +{ + u8 transaction_type = I3C_HUB_SMBUS_400kHz; + u8 controller_buffer_page = I3C_HUB_CONTROLLER_BUFFER_PAGE + 4 * target_port; + int write_length = xfers[nxfers_i].len; + int read_length = xfers[nxfers_i].len; + u8 target_port_status = I3C_HUB_TP0_SMBUS_AGNT_STS + target_port; + u8 addr = xfers[nxfers_i].addr; + u8 target_port_code = BIT(target_port); + u8 rw_address = 2 * addr; + u8 desc[I3C_HUB_SMBUS_DESCRIPTOR_SIZE] = { 0 }; + u8 status; + int ret; + + if (rw) + rw_address |= BIT(0); + else + read_length = 0; + + desc[0] = rw_address; + desc[1] = transaction_type; + desc[2] = write_length; + desc[3] = read_length; + + ret = regmap_write(priv->regmap, target_port_status, + I3C_HUB_TP_BUFFER_STATUS_MASK); + if (ret) + return ret; + + ret = regmap_write(priv->regmap, I3C_HUB_PAGE_PTR, controller_buffer_page); + if (ret) + return ret; + + ret = regmap_bulk_write(priv->regmap, I3C_HUB_CONTROLLER_AGENT_BUFF, + desc, I3C_HUB_SMBUS_DESCRIPTOR_SIZE); + if (ret) + return ret; + + if (!rw) { + ret = regmap_bulk_write(priv->regmap, + I3C_HUB_CONTROLLER_AGENT_BUFF_DATA, + xfers[nxfers_i].buf, xfers[nxfers_i].len); + if (ret) + return ret; + } + + ret = regmap_write(priv->regmap, I3C_HUB_TP_SMBUS_AGNT_TRANS_START, + target_port_code); + if (ret) + return ret; + + ret = i3c_hub_read_transaction_status(priv, target_port_status, &status); + if (ret) + return ret; + + *return_status = status; + + if (rw) { + ret = regmap_bulk_read(priv->regmap, I3C_HUB_CONTROLLER_AGENT_BUFF_DATA, + xfers[nxfers_i].buf, xfers[nxfers_i].len); + if (ret) + return ret; + } + + ret = regmap_write(priv->regmap, I3C_HUB_PAGE_PTR, 0x00); + if (ret) + return ret; + + return 0; +} + +/** + * i3c_controller_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_controller_smbus_port_adapter_xfer(struct i2c_adapter *adap, + struct i2c_msg *xfers, int nxfers) +{ + struct i3c_master_controller *controller = + container_of(adap, struct i3c_master_controller, i2c); + struct logical_bus *bus = + container_of(controller, struct logical_bus, controller); + struct i3c_hub *priv = bus->priv; + int ret_sum = 0; + int ret; + u8 return_status; + u8 nxfers_i; + u8 rw; + + for (nxfers_i = 0; nxfers_i < nxfers; nxfers_i++) { + if (xfers[nxfers_i].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; + } + + rw = xfers[nxfers_i].flags % 2; + + ret = i3c_hub_smbus_msg(priv, + xfers, + bus->smbus_port_adapter.tp_port, + nxfers_i, rw, &return_status); + if (ret) + return ret; + if (return_status == I3C_HUB_XFER_SUCCESS) + 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); + + return parent->ops->do_daa(parent); +} + +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); + ret = parent->ops->send_ccc_cmd(parent, cmd); + i3c_hub_trans_post_cb(bus); + + 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 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); + res = parent->ops->priv_xfers(dev, xfers, nxfers); + 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, + const 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); + ret = parent->ops->request_ibi(dev, req); + 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); + parent->ops->free_ibi(dev); + 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); + ret = parent->ops->enable_ibi(dev); + 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); + ret = parent->ops->disable_ibi(dev); + 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, + .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, +}; + +/* SMBus virtual i3c_master_controller_ops */ + +static int i3c_hub_do_daa_smbus(struct i3c_master_controller *controller) +{ + return 0; +} + +static int i3c_hub_send_ccc_cmd_smbus(struct i3c_master_controller *controller, + struct i3c_ccc_cmd *cmd) +{ + return 0; +} + +static int i3c_hub_priv_xfers_smbus(struct i3c_dev_desc *dev, + struct i3c_priv_xfer *xfers, int nxfers) +{ + return 0; +} + +static int i3c_hub_i2c_xfers_smbus(struct i2c_dev_desc *dev, + const struct i2c_msg *xfers, int nxfers) +{ + return 0; +} + +static const struct i3c_master_controller_ops i3c_hub_i3c_ops_smbus = { + .bus_init = i3c_hub_bus_init, + .bus_cleanup = i3c_hub_bus_cleanup, + .do_daa = i3c_hub_do_daa_smbus, + .send_ccc_cmd = i3c_hub_send_ccc_cmd_smbus, + .priv_xfers = i3c_hub_priv_xfers_smbus, + .i2c_xfers = i3c_hub_i2c_xfers_smbus, +}; + +int i3c_hub_logic_register(struct i3c_master_controller *controller, + struct device *parent) +{ + return i3c_master_register(controller, parent, &i3c_hub_i3c_ops, false); +} + +int i3c_hub_logic_register_smbus(struct i3c_master_controller *controller, + struct device *parent) +{ + return i3c_master_register(controller, parent, &i3c_hub_i3c_ops_smbus, false); +} + +static u32 i3c_controller_smbus_funcs(struct i2c_adapter *adapter) +{ + return I2C_FUNC_SMBUS_EMUL | I2C_FUNC_I2C; +} + +static int reg_i2c_target(struct i2c_client *client) +{ + return 0; +} + +static int unreg_i2c_target(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_algorithm i3c_controller_smbus_algo = { + .master_xfer = i3c_controller_smbus_port_adapter_xfer, + .functionality = i3c_controller_smbus_funcs, + .reg_slave = reg_i2c_target, + .unreg_slave = unreg_i2c_target, +}; + +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 i2c_board_info host_notify_board_info; + struct smbus_backend *backend = NULL; + struct logical_bus *bus; + int ret; + int i; + + for (i = 0; i < I3C_HUB_LOGICAL_BUS_MAX_COUNT; ++i) { + bus = &priv->logical_bus[i]; + if (bus->available) { + ret = regmap_write(priv->regmap, I3C_HUB_TP_NET_CON_CONF, bus->tp_map); + if (ret) + dev_warn(dev, "Failed to open Target Port(s)\n"); + + 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); + else + bus->registered = true; + + ret = regmap_write(priv->regmap, I3C_HUB_TP_NET_CON_CONF, 0x00); + if (ret) + dev_warn(dev, "Failed to close Target Port(s)\n"); + } + } + + for (i = 0; i < I3C_HUB_LOGICAL_BUS_MAX_COUNT; ++i) { + bus = &priv->logical_bus[i]; + if (bus->available) { + if (priv->settings.tp[i].always_enable) { + 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"); + } + } + } + + ret = i3c_master_do_daa(priv->driving_master); + if (ret) + dev_warn(dev, "Failed to run DAA\n"); + + for (i = 0; i < I3C_HUB_TP_MAX_COUNT; i++) { + bus = &priv->logical_bus[i]; + if (!bus->smbus_port_adapter.used) + continue; + + bus->controller.i2c.algo = &i3c_controller_smbus_algo; + + list_for_each_entry(backend, + &bus->smbus_port_adapter.backend_entry, list) { + host_notify_board_info.addr = backend->addr; + host_notify_board_info.flags = I2C_CLIENT_SLAVE; + snprintf(host_notify_board_info.type, + I2C_NAME_SIZE, backend->compatible); + + backend->client = i2c_new_client_device(&bus->controller.i2c, + &host_notify_board_info); + if (IS_ERR(backend->client)) { + dev_warn(dev, "Error while registering backend\n"); + return; + } + } + + schedule_delayed_work(&bus->smbus_port_adapter.delayed_work_polling, + msecs_to_jiffies(I3C_HUB_POLLING_ROLL_PERIOD_MS)); + } +} + +static int i3c_hub_register_smbus_controller(struct i3c_hub *priv, int i) +{ + struct device *dev = i3cdev_to_dev(priv->i3cdev); + int ret; + + dev->of_node = priv->logical_bus[i].of_node; + ret = i3c_hub_logic_register_smbus(&priv->logical_bus[i].controller, dev); + if (ret) { + dev_warn(dev, "Failed to register i3c controller\n"); + return ret; + } + + return 0; +} + +/* return true when backend is empty */ +static bool backend_is_empty(struct i2c_adapter_group *g_adap, struct i2c_adapter *adap) +{ + struct i2c_client *client, *next; + + if (!list_empty(&g_adap->backend_entry)) + return false; + + list_for_each_entry_safe(client, next, &adap->userspace_clients, detected) { + if (!strcmp(client->name, "slave-mqueue")) + return false; + } + + return true; +} + +/** + * i3c_hub_delayed_work_polling() - This delayed work is a polling mechanism to + * find if any transaction happened. After a transaction was found it is saved with + * the slave-mqueue backend and can be read from the fs. 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 void i3c_hub_delayed_work_polling(struct work_struct *work) +{ + struct i2c_adapter_group *g_adap = container_of(work, + typeof(*g_adap), delayed_work_polling.work); + struct logical_bus *bus = + container_of(g_adap, struct logical_bus, smbus_port_adapter); + u8 controller_buffer_page = + I3C_HUB_CONTROLLER_BUFFER_PAGE + 4 * g_adap->tp_port; + u8 target_port_status = I3C_HUB_TP0_SMBUS_AGNT_STS + g_adap->tp_port; + u8 local_buffer[I3C_HUB_SMBUS_TARGET_PAYLOAD_SIZE] = { 0 }; + u8 target_buffer_page, address, test, len, tmp; + struct i3c_hub *priv = bus->priv; + struct device *dev = i3cdev_to_dev(priv->i3cdev); + struct smbus_backend *backend = NULL; + u32 local_last_status, i; + bool found_backend = false; + struct i2c_client *client, *next; + struct i2c_adapter *adap; + + if (backend_is_empty(g_adap, &priv->logical_bus[g_adap->tp_port].controller.i2c)) { + schedule_delayed_work(&g_adap->delayed_work_polling, + msecs_to_jiffies(I3C_HUB_POLLING_ROLL_PERIOD_MS)); + return; + } + + regmap_read(priv->regmap, target_port_status, &local_last_status); + + tmp = local_last_status; + if (tmp & I3C_HUB_TARGET_BUF_OVRFL) { + regmap_write(priv->regmap, I3C_HUB_PAGE_PTR, 0x00); + regmap_write(priv->regmap, target_port_status, I3C_HUB_TP_BUFFER_STATUS_MASK); + regmap_read(priv->regmap, target_port_status, &local_last_status); + g_adap->polling_last_status = local_last_status; + } else if (local_last_status != g_adap->polling_last_status) { + if (tmp & I3C_HUB_TARGET_BUF_0_RECEIVE) + target_buffer_page = controller_buffer_page + 2; + else if (tmp & I3C_HUB_TARGET_BUF_1_RECEIVE) + target_buffer_page = controller_buffer_page + 3; + else + goto reschedule; + + regmap_write(priv->regmap, I3C_HUB_PAGE_PTR, target_buffer_page); + + regmap_read(priv->regmap, I3C_HUB_TARGET_BUFF_LENGTH, &local_last_status); + + len = local_last_status - 1; + if (len > I3C_HUB_SMBUS_TARGET_PAYLOAD_SIZE) { + dev_err(dev, "Received message too big for hub buffer\n"); + goto reschedule; + } + + regmap_read(priv->regmap, I3C_HUB_TARGET_BUFF_ADDRESS, &local_last_status); + address = local_last_status; + + regmap_bulk_read(priv->regmap, I3C_HUB_TARGET_BUFF_DATA, local_buffer, len); + + list_for_each_entry(backend, &g_adap->backend_entry, list) { + if (address >> 1 == backend->addr) { + i2c_slave_event(backend->client, I2C_SLAVE_WRITE_RECEIVED, + &address); + + for (i = 0; i < len; i++) { + tmp = local_buffer[i]; + i2c_slave_event(backend->client, I2C_SLAVE_WRITE_RECEIVED, + &tmp); + } + i2c_slave_event(backend->client, I2C_SLAVE_STOP, &test); + found_backend = true; + break; + } + } + + if (!found_backend) { + adap = &priv->logical_bus[g_adap->tp_port].controller.i2c; + list_for_each_entry_safe(client, next, &adap->userspace_clients, detected) { + if (client->addr == address >> 1 && + !strcmp(client->name, "slave-mqueue")) { + i2c_slave_event(client, I2C_SLAVE_WRITE_RECEIVED, &address); + + for (i = 0; i < len; i++) { + tmp = local_buffer[i]; + i2c_slave_event(client, I2C_SLAVE_WRITE_RECEIVED, + &tmp); + } + i2c_slave_event(client, I2C_SLAVE_STOP, &test); + break; + } + } + } + +reschedule: + regmap_write(priv->regmap, I3C_HUB_PAGE_PTR, 0x00); + regmap_write(priv->regmap, target_port_status, I3C_HUB_TP_BUFFER_STATUS_MASK); + regmap_read(priv->regmap, target_port_status, &local_last_status); + g_adap->polling_last_status = local_last_status; + } + + schedule_delayed_work(&g_adap->delayed_work_polling, + msecs_to_jiffies(I3C_HUB_POLLING_ROLL_PERIOD_MS)); +} + +static int i3c_hub_smbus_tp_algo(struct i3c_hub *priv, int i) +{ + struct device *dev = i3cdev_to_dev(priv->i3cdev); + int ret; + + if (priv->hub_dt_cp1_id != priv->hub_pin_cp1_id) { + dev_warn(dev, "hub_dt_cp1_id not equal to hub_pin_cp1_id!\n"); + return 1; + } + + priv->logical_bus[i].priv = priv; + priv->logical_bus[i].smbus_port_adapter.tp_port = i; + priv->logical_bus[i].smbus_port_adapter.tp_mask = BIT(i); + + /* Register controller for target port */ + ret = i3c_hub_register_smbus_controller(priv, i); + if (ret) + return ret; + + priv->logical_bus[i].smbus_port_adapter.used = 1; + + INIT_DELAYED_WORK(&priv->logical_bus[i].smbus_port_adapter.delayed_work_polling, + i3c_hub_delayed_work_polling); + + priv->logical_bus[i].controller.i2c.dev.parent = + priv->logical_bus[i].controller.dev.parent; + priv->logical_bus[i].controller.i2c.owner = + priv->logical_bus[i].controller.dev.parent->driver->owner; + + sprintf(priv->logical_bus[i].controller.i2c.name, "hub0x%X.port%d", + priv->hub_dt_cp1_id, i); + + priv->logical_bus[i].controller.i2c.timeout = 1000; + priv->logical_bus[i].controller.i2c.retries = 3; + + return 0; +} + +/* return true when backend node exist */ +static bool backend_node_is_exist(int port, struct i3c_hub *priv, u32 addr) +{ + struct smbus_backend *backend = NULL; + + list_for_each_entry(backend, + &priv->logical_bus[port].smbus_port_adapter.backend_entry, list) { + if (backend->addr == addr) + return true; + } + + return false; +} + +static int read_backend_from_i3c_hub_dts(struct device_node *i3c_node_target, + struct i3c_hub *priv) +{ + struct device_node *i3c_node_tp; + const char *compatible; + int tp_port, ret; + u32 addr_dts; + struct smbus_backend *backend; + + if (sscanf(i3c_node_target->full_name, "target-port@%d", &tp_port) == 0) + return -EINVAL; + + if (tp_port > I3C_HUB_TP_MAX_COUNT) + return -ERANGE; + + if (tp_port < 0) + return -EINVAL; + + INIT_LIST_HEAD(&priv->logical_bus[tp_port].smbus_port_adapter.backend_entry); + for_each_available_child_of_node(i3c_node_target, i3c_node_tp) { + if (strcmp(i3c_node_tp->name, "backend")) + continue; + + ret = of_property_read_u32(i3c_node_tp, "target-reg", &addr_dts); + if (ret) + return ret; + + if (backend_node_is_exist(tp_port, priv, addr_dts)) + continue; + + ret = of_property_read_string(i3c_node_tp, "compatible", &compatible); + if (ret) + return ret; + + /* Currently only the slave-mqueue backend is supported */ + if (strcmp("slave-mqueue", compatible)) + return -EINVAL; + + backend = kzalloc(sizeof(*backend), GFP_KERNEL); + if (!backend) + return -ENOMEM; + + backend->addr = addr_dts; + backend->compatible = compatible; + list_add(&backend->list, + &priv->logical_bus[tp_port].smbus_port_adapter.backend_entry); + } + + return 0; +} + +/** + * This function saves information about the i3c_hub's ports + * working in slave mode. It takes its data from the DTs + * (aspeed-bmc-intel-avc.dts) and saves the parameters + * into the coresponding target port i2c_adapter_group structure + * in the i3c_hub + * + * @dev: device used by i3c_hub + * @i3c_node_hub: device node pointing to the hub + * @priv: pointer to the i3c_hub structure + */ +static void i3c_hub_parse_dt_tp(struct device *dev, + const struct device_node *i3c_node_hub, + struct i3c_hub *priv) +{ + struct device_node *i3c_node_target; + int ret; + + for_each_available_child_of_node(i3c_node_hub, i3c_node_target) { + if (!strcmp(i3c_node_target->name, "target-port")) { + ret = read_backend_from_i3c_hub_dts(i3c_node_target, priv); + if (ret) + dev_err(dev, "DTS entry invalid - error %d", ret); + } + } +} + +static int i3c_hub_probe(struct i3c_device *i3cdev) +{ + struct regmap_config i3c_hub_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + }; + 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; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->i3cdev = i3cdev; + priv->driving_master = i3c_dev_get_master(i3cdev->desc); + i3cdev_set_drvdata(i3cdev, priv); + INIT_DELAYED_WORK(&priv->delayed_work, i3c_hub_delayed_work); + 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) + return dev_err_probe(dev, ret, "Failed to initialized DebugFS.\n"); + + 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"); + goto error; + } + priv->regmap = regmap; + + 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); + + /* Parse DTS to find data on the SMBus target mode */ + i3c_hub_parse_dt_tp(dev, node, priv); + } + + /* 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 < I3C_HUB_TP_MAX_COUNT; i++) { + priv->logical_bus[i].smbus_port_adapter.used = 0; + if (priv->settings.tp[i].mode == I3C_HUB_DT_TP_MODE_SMBUS) + ret = i3c_hub_smbus_tp_algo(priv, 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; + } + + /* TBD: Apply special/security lock here using DEV_CMD register */ + + schedule_delayed_work(&priv->delayed_work, msecs_to_jiffies(100)); + + return 0; + +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); + struct i2c_adapter_group *g_adap; + struct smbus_backend *backend = NULL; + int i; + + for (i = 0; i < I3C_HUB_TP_MAX_COUNT; i++) { + if (priv->logical_bus[i].smbus_port_adapter.used) { + g_adap = &priv->logical_bus[i].smbus_port_adapter; + cancel_delayed_work_sync(&g_adap->delayed_work_polling); + list_for_each_entry(backend, &g_adap->backend_entry, list) { + i2c_unregister_device(backend->client); + kfree(backend); + } + } + + if (priv->logical_bus[i].smbus_port_adapter.used || + 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 = "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/drivers/i3c/master.c b/drivers/i3c/master.c index 3afa530c5e32..b7cf15ba4e67 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -2244,15 +2244,26 @@ static int of_populate_i3c_bus(struct i3c_master_controller *master) struct device_node *node; int ret; u32 val; + bool ignore_hub_node = false; + char *addr_pid; if (!i3cbus_np) return 0; for_each_available_child_of_node(i3cbus_np, node) { - ret = of_i3c_master_add_dev(master, node); - if (ret) { - of_node_put(node); - return ret; + ignore_hub_node = false; + if (node->full_name && strstr(node->full_name, "hub")) { + addr_pid = strchr(node->full_name, '@'); + if (addr_pid && strcmp(addr_pid, "@0,0") == 0) + ignore_hub_node = true; + } + + if (!ignore_hub_node) { + ret = of_i3c_master_add_dev(master, node); + if (ret) { + of_node_put(node); + return ret; + } } } diff --git a/include/linux/i3c/device.h b/include/linux/i3c/device.h index e119f11948ef..5f3b7e571aed 100644 --- a/include/linux/i3c/device.h +++ b/include/linux/i3c/device.h @@ -74,9 +74,11 @@ 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, + I3C_DCR_HUB = 194, }; #define I3C_PID_MANUF_ID(pid) (((pid) & GENMASK_ULL(47, 33)) >> 33) -- 2.25.1
WARNING: multiple messages have this Message-ID (diff)
From: Steven Niu <steven.niu.uj@renesas.com> To: alexandre.belloni@bootlin.com, linux-i3c@lists.infradead.org, linux-kernel@vger.kernel.org, robh+dt@kernel.org, krzysztof.kozlowski+dt@linaro.org, conor+dt@kernel.org, devicetree@vger.kernel.org Cc: zbigniew.lukwinski@linux.intel.com, Steven Niu <steven.niu.uj@renesas.com> Subject: [PATCH 2/2] i3c: i3c-hub: Add Renesas RG3MxxB12A1 I3C HUB driver Date: Sat, 17 Feb 2024 21:14:12 +0800 [thread overview] Message-ID: <20240217131412.4145506-2-steven.niu.uj@renesas.com> (raw) In-Reply-To: <20240217131412.4145506-1-steven.niu.uj@renesas.com> RG3MxxB12A1 I3C HUB is smart device which provide multiple functionality: * Enabling voltage compatibility across I3C Controller and Target Device. * Enabling voltage compatibility across I3C Controller and Target devices. * Bus capacitance isolation. * Address conflict isolation. * I3C port expansion. * Two controllers in a single I3C bus. * I3C and SMBus device compatibility. * GPIO expansion. Signed-off-by: Steven Niu <steven.niu.uj@renesas.com> --- drivers/i3c/Kconfig | 12 + drivers/i3c/Makefile | 1 + drivers/i3c/i3c-hub.c | 1982 ++++++++++++++++++++++++++++++++++++ drivers/i3c/master.c | 19 +- include/linux/i3c/device.h | 2 + 5 files changed, 2012 insertions(+), 4 deletions(-) create mode 100644 drivers/i3c/i3c-hub.c diff --git a/drivers/i3c/Kconfig b/drivers/i3c/Kconfig index 30a441506f61..6b6a1f29bd33 100644 --- a/drivers/i3c/Kconfig +++ b/drivers/i3c/Kconfig @@ -21,4 +21,16 @@ menuconfig I3C if I3C 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. + endif # I3C diff --git a/drivers/i3c/Makefile b/drivers/i3c/Makefile index 11982efbc6d9..ca298faaee9a 100644 --- a/drivers/i3c/Makefile +++ b/drivers/i3c/Makefile @@ -2,3 +2,4 @@ i3c-y := device.o master.o obj-$(CONFIG_I3C) += i3c.o obj-$(CONFIG_I3C) += master/ +obj-$(CONFIG_I3C_HUB) += i3c-hub.o diff --git a/drivers/i3c/i3c-hub.c b/drivers/i3c/i3c-hub.c new file mode 100644 index 000000000000..73a9b96e6635 --- /dev/null +++ b/drivers/i3c/i3c-hub.c @@ -0,0 +1,1982 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2021 - 2023 Intel Corporation.*/ + +#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> + +#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 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 + +/* 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_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 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 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 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 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 + +/* 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 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 + +/* 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 */ +#define I3C_HUB_POLLING_ROLL_PERIOD_MS 10 + +/* 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 + +struct tp_setting { + u8 mode; + u8 pullup_en; + bool always_enable; +}; + +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]; +}; + +struct smbus_backend { + struct i2c_client *client; + const char *compatible; + int addr; + struct list_head list; +}; + +struct i2c_adapter_group { + u8 tp_mask; + u8 tp_port; + u8 used; + + struct delayed_work delayed_work_polling; + struct list_head backend_entry; + u8 polling_last_status; +}; + +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 i2c_adapter_group smbus_port_adapter; + struct device_node *of_node; + struct i3c_hub *priv; +}; + +struct i3c_hub { + struct i3c_device *i3cdev; + struct i3c_master_controller *driving_master; + struct regmap *regmap; + 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_LOGICAL_BUS_MAX_COUNT]; + + /* Offset for reading HUB's register. */ + u8 reg_addr; + struct dentry *debug_dir; +}; + +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 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 }, +}; + +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) { + if (ret != -EINVAL) /* Lack of property is not considered as a problem. */ + 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 void i3c_hub_tp_of_get_setting(struct device *dev, + const struct device_node *node, + struct tp_setting tp_setting[]) +{ + struct device_node *tp_node; + u32 id; + + for_each_available_child_of_node(node, tp_node) { + if (!tp_node->name || of_node_cmp(tp_node->name, "target-port")) + continue; + + if (!tp_node->full_name || + (sscanf(tp_node->full_name, "target-port@%u", &id) != 1)) { + dev_warn(dev, "Invalid target port node found in DT: %s\n", + tp_node->full_name); + continue; + } + + if (id >= I3C_HUB_TP_MAX_COUNT) { + dev_warn(dev, "Invalid target port index found in DT: %i\n", id); + continue; + } + 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); + tp_setting[id].always_enable = + of_property_read_bool(tp_node, "always-enable"); + } +} + +static void i3c_hub_of_get_conf_static(struct device *dev, + const struct device_node *node) +{ + struct i3c_hub *priv = dev_get_drvdata(dev); + + 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); + + i3c_hub_tp_of_get_setting(dev, node, priv->settings.tp); +} + +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; + + 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; + } +} + +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; + int ret; + int i; + + /* TBD: Read type of HUB from register I3C_HUB_DEV_INFO_0 to learn target ports count. */ + for (i = 0; i < I3C_HUB_TP_MAX_COUNT; ++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); + + 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); + } + 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); + } + } + + ret = regmap_update_bits(priv->regmap, I3C_HUB_TP_IO_MODE_CONF, smbus_mask, smbus_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_update_bits(priv->regmap, I3C_HUB_TP_NET_CON_CONF, i3c_mask, i3c_val); +} + +static int i3c_hub_configure_hw(struct device *dev) +{ + int ret; + + 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; + + return i3c_hub_hw_configure_tp(dev); +} + +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 < I3C_HUB_LOGICAL_BUS_MAX_COUNT) { + 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; + 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 << 4 | (u32)priv->hub_dt_sel_id; + pin_id = (u32)priv->hub_pin_cp1_id << 4 | (u32)priv->hub_pin_sel_id; + + if ((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; + + 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); + + for (i = 0; i < I3C_HUB_TP_MAX_COUNT; ++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); + } + + 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_status, u8 *status) +{ + unsigned long time_to_timeout = 0; + unsigned int status_read; + ktime_t start, end; + int ret; + + start = ktime_get_real(); + + while (time_to_timeout < (long)I3C_HUB_SMBUS_400kHz_TIMEOUT) { + ret = regmap_read(priv->regmap, target_port_status, &status_read); + if (ret) + return ret; + + *status = (u8)status_read; + + if ((*status & I3C_HUB_TP_BUFFER_STATUS_MASK) == I3C_HUB_XFER_SUCCESS) + return 0; + + if (!(*status & I3C_HUB_TP_BUFFER_STATUS_MASK) && + (*status & I3C_HUB_TP_TRANSACTION_CODE_MASK)) { + dev_err(&priv->i3cdev->dev, "Invalid transfer status returned\n"); + return 0; + } + + end = ktime_get_real(); + time_to_timeout = end - start; + } + dev_err(&priv->i3cdev->dev, "Status read timeout reached\n"); + return 0; +} + +/* + * i3c_hub_smbus_msg() - This starts a smbus write 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. + * @priv: a pointer to the i3c hub main structure + * @ssport: 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 + * @rw: number informing if the message is of read or write type (0 for write, 1 for read) + * @return_status: number passed by reference where the return status code is saved + * + * Return: on success function returns zero. Otherwise the regmap read or write error code + * is returned + */ +static int i3c_hub_smbus_msg(struct i3c_hub *priv, + struct i2c_msg *xfers, + u8 target_port, + u8 nxfers_i, u8 rw, u8 *return_status) +{ + u8 transaction_type = I3C_HUB_SMBUS_400kHz; + u8 controller_buffer_page = I3C_HUB_CONTROLLER_BUFFER_PAGE + 4 * target_port; + int write_length = xfers[nxfers_i].len; + int read_length = xfers[nxfers_i].len; + u8 target_port_status = I3C_HUB_TP0_SMBUS_AGNT_STS + target_port; + u8 addr = xfers[nxfers_i].addr; + u8 target_port_code = BIT(target_port); + u8 rw_address = 2 * addr; + u8 desc[I3C_HUB_SMBUS_DESCRIPTOR_SIZE] = { 0 }; + u8 status; + int ret; + + if (rw) + rw_address |= BIT(0); + else + read_length = 0; + + desc[0] = rw_address; + desc[1] = transaction_type; + desc[2] = write_length; + desc[3] = read_length; + + ret = regmap_write(priv->regmap, target_port_status, + I3C_HUB_TP_BUFFER_STATUS_MASK); + if (ret) + return ret; + + ret = regmap_write(priv->regmap, I3C_HUB_PAGE_PTR, controller_buffer_page); + if (ret) + return ret; + + ret = regmap_bulk_write(priv->regmap, I3C_HUB_CONTROLLER_AGENT_BUFF, + desc, I3C_HUB_SMBUS_DESCRIPTOR_SIZE); + if (ret) + return ret; + + if (!rw) { + ret = regmap_bulk_write(priv->regmap, + I3C_HUB_CONTROLLER_AGENT_BUFF_DATA, + xfers[nxfers_i].buf, xfers[nxfers_i].len); + if (ret) + return ret; + } + + ret = regmap_write(priv->regmap, I3C_HUB_TP_SMBUS_AGNT_TRANS_START, + target_port_code); + if (ret) + return ret; + + ret = i3c_hub_read_transaction_status(priv, target_port_status, &status); + if (ret) + return ret; + + *return_status = status; + + if (rw) { + ret = regmap_bulk_read(priv->regmap, I3C_HUB_CONTROLLER_AGENT_BUFF_DATA, + xfers[nxfers_i].buf, xfers[nxfers_i].len); + if (ret) + return ret; + } + + ret = regmap_write(priv->regmap, I3C_HUB_PAGE_PTR, 0x00); + if (ret) + return ret; + + return 0; +} + +/** + * i3c_controller_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_controller_smbus_port_adapter_xfer(struct i2c_adapter *adap, + struct i2c_msg *xfers, int nxfers) +{ + struct i3c_master_controller *controller = + container_of(adap, struct i3c_master_controller, i2c); + struct logical_bus *bus = + container_of(controller, struct logical_bus, controller); + struct i3c_hub *priv = bus->priv; + int ret_sum = 0; + int ret; + u8 return_status; + u8 nxfers_i; + u8 rw; + + for (nxfers_i = 0; nxfers_i < nxfers; nxfers_i++) { + if (xfers[nxfers_i].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; + } + + rw = xfers[nxfers_i].flags % 2; + + ret = i3c_hub_smbus_msg(priv, + xfers, + bus->smbus_port_adapter.tp_port, + nxfers_i, rw, &return_status); + if (ret) + return ret; + if (return_status == I3C_HUB_XFER_SUCCESS) + 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); + + return parent->ops->do_daa(parent); +} + +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); + ret = parent->ops->send_ccc_cmd(parent, cmd); + i3c_hub_trans_post_cb(bus); + + 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 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); + res = parent->ops->priv_xfers(dev, xfers, nxfers); + 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, + const 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); + ret = parent->ops->request_ibi(dev, req); + 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); + parent->ops->free_ibi(dev); + 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); + ret = parent->ops->enable_ibi(dev); + 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); + ret = parent->ops->disable_ibi(dev); + 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, + .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, +}; + +/* SMBus virtual i3c_master_controller_ops */ + +static int i3c_hub_do_daa_smbus(struct i3c_master_controller *controller) +{ + return 0; +} + +static int i3c_hub_send_ccc_cmd_smbus(struct i3c_master_controller *controller, + struct i3c_ccc_cmd *cmd) +{ + return 0; +} + +static int i3c_hub_priv_xfers_smbus(struct i3c_dev_desc *dev, + struct i3c_priv_xfer *xfers, int nxfers) +{ + return 0; +} + +static int i3c_hub_i2c_xfers_smbus(struct i2c_dev_desc *dev, + const struct i2c_msg *xfers, int nxfers) +{ + return 0; +} + +static const struct i3c_master_controller_ops i3c_hub_i3c_ops_smbus = { + .bus_init = i3c_hub_bus_init, + .bus_cleanup = i3c_hub_bus_cleanup, + .do_daa = i3c_hub_do_daa_smbus, + .send_ccc_cmd = i3c_hub_send_ccc_cmd_smbus, + .priv_xfers = i3c_hub_priv_xfers_smbus, + .i2c_xfers = i3c_hub_i2c_xfers_smbus, +}; + +int i3c_hub_logic_register(struct i3c_master_controller *controller, + struct device *parent) +{ + return i3c_master_register(controller, parent, &i3c_hub_i3c_ops, false); +} + +int i3c_hub_logic_register_smbus(struct i3c_master_controller *controller, + struct device *parent) +{ + return i3c_master_register(controller, parent, &i3c_hub_i3c_ops_smbus, false); +} + +static u32 i3c_controller_smbus_funcs(struct i2c_adapter *adapter) +{ + return I2C_FUNC_SMBUS_EMUL | I2C_FUNC_I2C; +} + +static int reg_i2c_target(struct i2c_client *client) +{ + return 0; +} + +static int unreg_i2c_target(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_algorithm i3c_controller_smbus_algo = { + .master_xfer = i3c_controller_smbus_port_adapter_xfer, + .functionality = i3c_controller_smbus_funcs, + .reg_slave = reg_i2c_target, + .unreg_slave = unreg_i2c_target, +}; + +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 i2c_board_info host_notify_board_info; + struct smbus_backend *backend = NULL; + struct logical_bus *bus; + int ret; + int i; + + for (i = 0; i < I3C_HUB_LOGICAL_BUS_MAX_COUNT; ++i) { + bus = &priv->logical_bus[i]; + if (bus->available) { + ret = regmap_write(priv->regmap, I3C_HUB_TP_NET_CON_CONF, bus->tp_map); + if (ret) + dev_warn(dev, "Failed to open Target Port(s)\n"); + + 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); + else + bus->registered = true; + + ret = regmap_write(priv->regmap, I3C_HUB_TP_NET_CON_CONF, 0x00); + if (ret) + dev_warn(dev, "Failed to close Target Port(s)\n"); + } + } + + for (i = 0; i < I3C_HUB_LOGICAL_BUS_MAX_COUNT; ++i) { + bus = &priv->logical_bus[i]; + if (bus->available) { + if (priv->settings.tp[i].always_enable) { + 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"); + } + } + } + + ret = i3c_master_do_daa(priv->driving_master); + if (ret) + dev_warn(dev, "Failed to run DAA\n"); + + for (i = 0; i < I3C_HUB_TP_MAX_COUNT; i++) { + bus = &priv->logical_bus[i]; + if (!bus->smbus_port_adapter.used) + continue; + + bus->controller.i2c.algo = &i3c_controller_smbus_algo; + + list_for_each_entry(backend, + &bus->smbus_port_adapter.backend_entry, list) { + host_notify_board_info.addr = backend->addr; + host_notify_board_info.flags = I2C_CLIENT_SLAVE; + snprintf(host_notify_board_info.type, + I2C_NAME_SIZE, backend->compatible); + + backend->client = i2c_new_client_device(&bus->controller.i2c, + &host_notify_board_info); + if (IS_ERR(backend->client)) { + dev_warn(dev, "Error while registering backend\n"); + return; + } + } + + schedule_delayed_work(&bus->smbus_port_adapter.delayed_work_polling, + msecs_to_jiffies(I3C_HUB_POLLING_ROLL_PERIOD_MS)); + } +} + +static int i3c_hub_register_smbus_controller(struct i3c_hub *priv, int i) +{ + struct device *dev = i3cdev_to_dev(priv->i3cdev); + int ret; + + dev->of_node = priv->logical_bus[i].of_node; + ret = i3c_hub_logic_register_smbus(&priv->logical_bus[i].controller, dev); + if (ret) { + dev_warn(dev, "Failed to register i3c controller\n"); + return ret; + } + + return 0; +} + +/* return true when backend is empty */ +static bool backend_is_empty(struct i2c_adapter_group *g_adap, struct i2c_adapter *adap) +{ + struct i2c_client *client, *next; + + if (!list_empty(&g_adap->backend_entry)) + return false; + + list_for_each_entry_safe(client, next, &adap->userspace_clients, detected) { + if (!strcmp(client->name, "slave-mqueue")) + return false; + } + + return true; +} + +/** + * i3c_hub_delayed_work_polling() - This delayed work is a polling mechanism to + * find if any transaction happened. After a transaction was found it is saved with + * the slave-mqueue backend and can be read from the fs. 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 void i3c_hub_delayed_work_polling(struct work_struct *work) +{ + struct i2c_adapter_group *g_adap = container_of(work, + typeof(*g_adap), delayed_work_polling.work); + struct logical_bus *bus = + container_of(g_adap, struct logical_bus, smbus_port_adapter); + u8 controller_buffer_page = + I3C_HUB_CONTROLLER_BUFFER_PAGE + 4 * g_adap->tp_port; + u8 target_port_status = I3C_HUB_TP0_SMBUS_AGNT_STS + g_adap->tp_port; + u8 local_buffer[I3C_HUB_SMBUS_TARGET_PAYLOAD_SIZE] = { 0 }; + u8 target_buffer_page, address, test, len, tmp; + struct i3c_hub *priv = bus->priv; + struct device *dev = i3cdev_to_dev(priv->i3cdev); + struct smbus_backend *backend = NULL; + u32 local_last_status, i; + bool found_backend = false; + struct i2c_client *client, *next; + struct i2c_adapter *adap; + + if (backend_is_empty(g_adap, &priv->logical_bus[g_adap->tp_port].controller.i2c)) { + schedule_delayed_work(&g_adap->delayed_work_polling, + msecs_to_jiffies(I3C_HUB_POLLING_ROLL_PERIOD_MS)); + return; + } + + regmap_read(priv->regmap, target_port_status, &local_last_status); + + tmp = local_last_status; + if (tmp & I3C_HUB_TARGET_BUF_OVRFL) { + regmap_write(priv->regmap, I3C_HUB_PAGE_PTR, 0x00); + regmap_write(priv->regmap, target_port_status, I3C_HUB_TP_BUFFER_STATUS_MASK); + regmap_read(priv->regmap, target_port_status, &local_last_status); + g_adap->polling_last_status = local_last_status; + } else if (local_last_status != g_adap->polling_last_status) { + if (tmp & I3C_HUB_TARGET_BUF_0_RECEIVE) + target_buffer_page = controller_buffer_page + 2; + else if (tmp & I3C_HUB_TARGET_BUF_1_RECEIVE) + target_buffer_page = controller_buffer_page + 3; + else + goto reschedule; + + regmap_write(priv->regmap, I3C_HUB_PAGE_PTR, target_buffer_page); + + regmap_read(priv->regmap, I3C_HUB_TARGET_BUFF_LENGTH, &local_last_status); + + len = local_last_status - 1; + if (len > I3C_HUB_SMBUS_TARGET_PAYLOAD_SIZE) { + dev_err(dev, "Received message too big for hub buffer\n"); + goto reschedule; + } + + regmap_read(priv->regmap, I3C_HUB_TARGET_BUFF_ADDRESS, &local_last_status); + address = local_last_status; + + regmap_bulk_read(priv->regmap, I3C_HUB_TARGET_BUFF_DATA, local_buffer, len); + + list_for_each_entry(backend, &g_adap->backend_entry, list) { + if (address >> 1 == backend->addr) { + i2c_slave_event(backend->client, I2C_SLAVE_WRITE_RECEIVED, + &address); + + for (i = 0; i < len; i++) { + tmp = local_buffer[i]; + i2c_slave_event(backend->client, I2C_SLAVE_WRITE_RECEIVED, + &tmp); + } + i2c_slave_event(backend->client, I2C_SLAVE_STOP, &test); + found_backend = true; + break; + } + } + + if (!found_backend) { + adap = &priv->logical_bus[g_adap->tp_port].controller.i2c; + list_for_each_entry_safe(client, next, &adap->userspace_clients, detected) { + if (client->addr == address >> 1 && + !strcmp(client->name, "slave-mqueue")) { + i2c_slave_event(client, I2C_SLAVE_WRITE_RECEIVED, &address); + + for (i = 0; i < len; i++) { + tmp = local_buffer[i]; + i2c_slave_event(client, I2C_SLAVE_WRITE_RECEIVED, + &tmp); + } + i2c_slave_event(client, I2C_SLAVE_STOP, &test); + break; + } + } + } + +reschedule: + regmap_write(priv->regmap, I3C_HUB_PAGE_PTR, 0x00); + regmap_write(priv->regmap, target_port_status, I3C_HUB_TP_BUFFER_STATUS_MASK); + regmap_read(priv->regmap, target_port_status, &local_last_status); + g_adap->polling_last_status = local_last_status; + } + + schedule_delayed_work(&g_adap->delayed_work_polling, + msecs_to_jiffies(I3C_HUB_POLLING_ROLL_PERIOD_MS)); +} + +static int i3c_hub_smbus_tp_algo(struct i3c_hub *priv, int i) +{ + struct device *dev = i3cdev_to_dev(priv->i3cdev); + int ret; + + if (priv->hub_dt_cp1_id != priv->hub_pin_cp1_id) { + dev_warn(dev, "hub_dt_cp1_id not equal to hub_pin_cp1_id!\n"); + return 1; + } + + priv->logical_bus[i].priv = priv; + priv->logical_bus[i].smbus_port_adapter.tp_port = i; + priv->logical_bus[i].smbus_port_adapter.tp_mask = BIT(i); + + /* Register controller for target port */ + ret = i3c_hub_register_smbus_controller(priv, i); + if (ret) + return ret; + + priv->logical_bus[i].smbus_port_adapter.used = 1; + + INIT_DELAYED_WORK(&priv->logical_bus[i].smbus_port_adapter.delayed_work_polling, + i3c_hub_delayed_work_polling); + + priv->logical_bus[i].controller.i2c.dev.parent = + priv->logical_bus[i].controller.dev.parent; + priv->logical_bus[i].controller.i2c.owner = + priv->logical_bus[i].controller.dev.parent->driver->owner; + + sprintf(priv->logical_bus[i].controller.i2c.name, "hub0x%X.port%d", + priv->hub_dt_cp1_id, i); + + priv->logical_bus[i].controller.i2c.timeout = 1000; + priv->logical_bus[i].controller.i2c.retries = 3; + + return 0; +} + +/* return true when backend node exist */ +static bool backend_node_is_exist(int port, struct i3c_hub *priv, u32 addr) +{ + struct smbus_backend *backend = NULL; + + list_for_each_entry(backend, + &priv->logical_bus[port].smbus_port_adapter.backend_entry, list) { + if (backend->addr == addr) + return true; + } + + return false; +} + +static int read_backend_from_i3c_hub_dts(struct device_node *i3c_node_target, + struct i3c_hub *priv) +{ + struct device_node *i3c_node_tp; + const char *compatible; + int tp_port, ret; + u32 addr_dts; + struct smbus_backend *backend; + + if (sscanf(i3c_node_target->full_name, "target-port@%d", &tp_port) == 0) + return -EINVAL; + + if (tp_port > I3C_HUB_TP_MAX_COUNT) + return -ERANGE; + + if (tp_port < 0) + return -EINVAL; + + INIT_LIST_HEAD(&priv->logical_bus[tp_port].smbus_port_adapter.backend_entry); + for_each_available_child_of_node(i3c_node_target, i3c_node_tp) { + if (strcmp(i3c_node_tp->name, "backend")) + continue; + + ret = of_property_read_u32(i3c_node_tp, "target-reg", &addr_dts); + if (ret) + return ret; + + if (backend_node_is_exist(tp_port, priv, addr_dts)) + continue; + + ret = of_property_read_string(i3c_node_tp, "compatible", &compatible); + if (ret) + return ret; + + /* Currently only the slave-mqueue backend is supported */ + if (strcmp("slave-mqueue", compatible)) + return -EINVAL; + + backend = kzalloc(sizeof(*backend), GFP_KERNEL); + if (!backend) + return -ENOMEM; + + backend->addr = addr_dts; + backend->compatible = compatible; + list_add(&backend->list, + &priv->logical_bus[tp_port].smbus_port_adapter.backend_entry); + } + + return 0; +} + +/** + * This function saves information about the i3c_hub's ports + * working in slave mode. It takes its data from the DTs + * (aspeed-bmc-intel-avc.dts) and saves the parameters + * into the coresponding target port i2c_adapter_group structure + * in the i3c_hub + * + * @dev: device used by i3c_hub + * @i3c_node_hub: device node pointing to the hub + * @priv: pointer to the i3c_hub structure + */ +static void i3c_hub_parse_dt_tp(struct device *dev, + const struct device_node *i3c_node_hub, + struct i3c_hub *priv) +{ + struct device_node *i3c_node_target; + int ret; + + for_each_available_child_of_node(i3c_node_hub, i3c_node_target) { + if (!strcmp(i3c_node_target->name, "target-port")) { + ret = read_backend_from_i3c_hub_dts(i3c_node_target, priv); + if (ret) + dev_err(dev, "DTS entry invalid - error %d", ret); + } + } +} + +static int i3c_hub_probe(struct i3c_device *i3cdev) +{ + struct regmap_config i3c_hub_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + }; + 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; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->i3cdev = i3cdev; + priv->driving_master = i3c_dev_get_master(i3cdev->desc); + i3cdev_set_drvdata(i3cdev, priv); + INIT_DELAYED_WORK(&priv->delayed_work, i3c_hub_delayed_work); + 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) + return dev_err_probe(dev, ret, "Failed to initialized DebugFS.\n"); + + 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"); + goto error; + } + priv->regmap = regmap; + + 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); + + /* Parse DTS to find data on the SMBus target mode */ + i3c_hub_parse_dt_tp(dev, node, priv); + } + + /* 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 < I3C_HUB_TP_MAX_COUNT; i++) { + priv->logical_bus[i].smbus_port_adapter.used = 0; + if (priv->settings.tp[i].mode == I3C_HUB_DT_TP_MODE_SMBUS) + ret = i3c_hub_smbus_tp_algo(priv, 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; + } + + /* TBD: Apply special/security lock here using DEV_CMD register */ + + schedule_delayed_work(&priv->delayed_work, msecs_to_jiffies(100)); + + return 0; + +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); + struct i2c_adapter_group *g_adap; + struct smbus_backend *backend = NULL; + int i; + + for (i = 0; i < I3C_HUB_TP_MAX_COUNT; i++) { + if (priv->logical_bus[i].smbus_port_adapter.used) { + g_adap = &priv->logical_bus[i].smbus_port_adapter; + cancel_delayed_work_sync(&g_adap->delayed_work_polling); + list_for_each_entry(backend, &g_adap->backend_entry, list) { + i2c_unregister_device(backend->client); + kfree(backend); + } + } + + if (priv->logical_bus[i].smbus_port_adapter.used || + 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 = "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/drivers/i3c/master.c b/drivers/i3c/master.c index 3afa530c5e32..b7cf15ba4e67 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -2244,15 +2244,26 @@ static int of_populate_i3c_bus(struct i3c_master_controller *master) struct device_node *node; int ret; u32 val; + bool ignore_hub_node = false; + char *addr_pid; if (!i3cbus_np) return 0; for_each_available_child_of_node(i3cbus_np, node) { - ret = of_i3c_master_add_dev(master, node); - if (ret) { - of_node_put(node); - return ret; + ignore_hub_node = false; + if (node->full_name && strstr(node->full_name, "hub")) { + addr_pid = strchr(node->full_name, '@'); + if (addr_pid && strcmp(addr_pid, "@0,0") == 0) + ignore_hub_node = true; + } + + if (!ignore_hub_node) { + ret = of_i3c_master_add_dev(master, node); + if (ret) { + of_node_put(node); + return ret; + } } } diff --git a/include/linux/i3c/device.h b/include/linux/i3c/device.h index e119f11948ef..5f3b7e571aed 100644 --- a/include/linux/i3c/device.h +++ b/include/linux/i3c/device.h @@ -74,9 +74,11 @@ 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, + I3C_DCR_HUB = 194, }; #define I3C_PID_MANUF_ID(pid) (((pid) & GENMASK_ULL(47, 33)) >> 33) -- 2.25.1 -- linux-i3c mailing list linux-i3c@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-i3c
next prev parent reply other threads:[~2024-02-17 13:14 UTC|newest] Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top 2024-02-17 13:14 [PATCH 1/2] dt-bindings: i3c-hub: Add Renesas RG3MxxB12A1 I3C HUB Steven Niu 2024-02-17 13:14 ` Steven Niu 2024-02-17 13:14 ` Steven Niu [this message] 2024-02-17 13:14 ` [PATCH 2/2] i3c: i3c-hub: Add Renesas RG3MxxB12A1 I3C HUB driver Steven Niu 2024-02-17 13:58 ` Krzysztof Kozlowski 2024-02-17 13:58 ` Krzysztof Kozlowski 2024-02-17 13:53 ` [PATCH 1/2] dt-bindings: i3c-hub: Add Renesas RG3MxxB12A1 I3C HUB Krzysztof Kozlowski 2024-02-17 13:53 ` Krzysztof Kozlowski 2024-02-17 13:57 ` Krzysztof Kozlowski 2024-02-17 13:57 ` Krzysztof Kozlowski 2024-02-17 14:07 ` Krzysztof Kozlowski 2024-02-17 14:07 ` Krzysztof Kozlowski 2024-02-17 14:55 ` Rob Herring 2024-02-17 14:55 ` Rob Herring
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=20240217131412.4145506-2-steven.niu.uj@renesas.com \ --to=steven.niu.uj@renesas.com \ --cc=alexandre.belloni@bootlin.com \ --cc=conor+dt@kernel.org \ --cc=devicetree@vger.kernel.org \ --cc=krzysztof.kozlowski+dt@linaro.org \ --cc=linux-i3c@lists.infradead.org \ --cc=linux-kernel@vger.kernel.org \ --cc=robh+dt@kernel.org \ --cc=zbigniew.lukwinski@linux.intel.com \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: linkBe sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.