blob: 803e3e30784fa2916500d2171998fe78792f7653 [file] [log] [blame]
From 0447505c0b877e70521171d11006b72b2ef9aef6 Mon Sep 17 00:00:00 2001
From: David Wang <davidwang@quantatw.com>
Date: Mon, 6 Nov 2023 16:01:32 +0800
Subject: [PATCH] drivers: i3c/mctp: sync npcm i3c/mctp
---
drivers/i3c/Kconfig | 31 +
drivers/i3c/Makefile | 4 +
drivers/i3c/device.c | 161 ++
drivers/i3c/i3c-hub.c | 708 ++++++++
drivers/i3c/i3c-npcm-bic.c | 237 +++
drivers/i3c/i3cdev.c | 435 +++++
drivers/i3c/internals.h | 12 +
drivers/i3c/master.c | 537 +++++-
drivers/i3c/master/i3c-master-cdns.c | 13 +-
drivers/i3c/master/mipi-i3c-hci/dat_v1.c | 29 +-
drivers/i3c/master/mipi-i3c-hci/dma.c | 12 +-
drivers/i3c/master/svc-i3c-master.c | 1884 ++++++++++++++++++----
drivers/i3c/mctp/Kconfig | 14 +
drivers/i3c/mctp/Makefile | 3 +
drivers/i3c/mctp/i3c-mctp.c | 624 +++++++
drivers/i3c/mctp/i3c-target-mctp.c | 389 +++++
drivers/net/mctp/Kconfig | 9 +
drivers/net/mctp/Makefile | 1 +
drivers/net/mctp/mctp-i3c.c | 777 +++++++++
include/linux/i3c/ccc.h | 11 +
include/linux/i3c/device.h | 19 +
include/linux/i3c/master.h | 37 +
include/linux/i3c/mctp/i3c-mctp.h | 50 +
include/linux/i3c/target.h | 23 +
include/uapi/linux/i3c/i3cdev.h | 38 +
net/mctp/af_mctp.c | 7 -
net/mctp/route.c | 6 -
27 files changed, 5728 insertions(+), 343 deletions(-)
create mode 100644 drivers/i3c/i3c-hub.c
create mode 100644 drivers/i3c/i3c-npcm-bic.c
create mode 100644 drivers/i3c/i3cdev.c
create mode 100644 drivers/i3c/mctp/Kconfig
create mode 100644 drivers/i3c/mctp/Makefile
create mode 100644 drivers/i3c/mctp/i3c-mctp.c
create mode 100644 drivers/i3c/mctp/i3c-target-mctp.c
create mode 100644 drivers/net/mctp/mctp-i3c.c
create mode 100644 include/linux/i3c/mctp/i3c-mctp.h
create mode 100644 include/linux/i3c/target.h
create mode 100644 include/uapi/linux/i3c/i3cdev.h
diff --git a/drivers/i3c/Kconfig b/drivers/i3c/Kconfig
index 30a441506f61..fe6093cd4973 100644
--- a/drivers/i3c/Kconfig
+++ b/drivers/i3c/Kconfig
@@ -20,5 +20,36 @@ menuconfig I3C
will be called i3c.
if I3C
+
+source "drivers/i3c/mctp/Kconfig"
+
+config I3CDEV
+ tristate "I3C device interface"
+ depends on I3C
+ help
+ Say Y here to use i3c-* device files, usually found in the /dev
+ directory on your system. They make it possible to have user-space
+ programs use the I3C devices.
+
+ This support is also available as a module. If so, the module
+ will be called i3cdev.
+
+ Note that this application programming interface is EXPERIMENTAL
+ and hence SUBJECT TO CHANGE WITHOUT NOTICE while it stabilizes.
+
+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.
+
+config I3C_NPCM_BIC
+ bool "NPCM Bridge IC support"
+ default y
+ help
+ Say Y here to enable NPCM Bridge IC device driver.
+
source "drivers/i3c/master/Kconfig"
endif # I3C
diff --git a/drivers/i3c/Makefile b/drivers/i3c/Makefile
index 11982efbc6d9..04303dc59aa3 100644
--- a/drivers/i3c/Makefile
+++ b/drivers/i3c/Makefile
@@ -1,4 +1,8 @@
# SPDX-License-Identifier: GPL-2.0
i3c-y := device.o master.o
obj-$(CONFIG_I3C) += i3c.o
+obj-$(CONFIG_I3CDEV) += i3cdev.o
obj-$(CONFIG_I3C) += master/
+obj-$(CONFIG_I3C) += mctp/
+obj-$(CONFIG_I3C_HUB) += i3c-hub.o
+obj-$(CONFIG_I3C_NPCM_BIC) += i3c-npcm-bic.o
diff --git a/drivers/i3c/device.c b/drivers/i3c/device.c
index e92d3e9a52bd..675816b1c073 100644
--- a/drivers/i3c/device.c
+++ b/drivers/i3c/device.c
@@ -50,6 +50,29 @@ int i3c_device_do_priv_xfers(struct i3c_device *dev,
}
EXPORT_SYMBOL_GPL(i3c_device_do_priv_xfers);
+/**
+ * i3c_device_generate_ibi() - request In-Band Interrupt
+ *
+ * @dev: target device
+ * @data: IBI payload
+ * @len: payload length in bytes
+ *
+ * Request In-Band Interrupt with or without data payload.
+ *
+ * Return: 0 in case of success, a negative error code otherwise.
+ */
+int i3c_device_generate_ibi(struct i3c_device *dev, const u8 *data, int len)
+{
+ int ret;
+
+ i3c_bus_normaluse_lock(dev->bus);
+ ret = i3c_dev_generate_ibi_locked(dev->desc, data, len);
+ i3c_bus_normaluse_unlock(dev->bus);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(i3c_device_generate_ibi);
+
/**
* i3c_device_get_info() - get I3C device information
*
@@ -176,6 +199,20 @@ void i3c_device_free_ibi(struct i3c_device *dev)
}
EXPORT_SYMBOL_GPL(i3c_device_free_ibi);
+int i3c_device_send_ccc_cmd(struct i3c_device *dev, u8 ccc_id)
+{
+ int ret;
+
+ if (dev->desc) {
+ i3c_bus_normaluse_lock(dev->bus);
+ ret = i3c_dev_send_ccc_cmd_locked(dev->desc, ccc_id);
+ i3c_bus_normaluse_unlock(dev->bus);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(i3c_device_send_ccc_cmd);
+
/**
* i3cdev_to_dev() - Returns the device embedded in @i3cdev
* @i3cdev: I3C device
@@ -283,3 +320,127 @@ void i3c_driver_unregister(struct i3c_driver *drv)
driver_unregister(&drv->driver);
}
EXPORT_SYMBOL_GPL(i3c_driver_unregister);
+
+/**
+ * i3c_device_getstatus_ccc() - receive device status
+ *
+ * @dev: I3C device to get the status for
+ * @info: I3C device info to fill the status in
+ *
+ * Receive I3C device status from I3C master device via corresponding CCC
+ * command
+ *
+ * Return: 0 in case of success, a negative error code otherwise.
+ */
+int i3c_device_getstatus_ccc(struct i3c_device *dev, struct i3c_device_info *info)
+{
+ int ret = -EINVAL;
+
+ i3c_bus_normaluse_lock(dev->bus);
+ if (dev->desc)
+ ret = i3c_dev_getstatus_locked(dev->desc, info);
+ i3c_bus_normaluse_unlock(dev->bus);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(i3c_device_getstatus_ccc);
+
+/**
+ * i3c_device_setmrl_ccc() - set maximum read length
+ *
+ * @dev: I3C device to set the length for
+ * @info: I3C device info to fill the length in
+ * @read_len: maximum read length value to be set
+ * @ibi_len: maximum ibi payload length to be set
+ *
+ * Set I3C device maximum read length from I3C master device via corresponding CCC command
+ *
+ * Return: 0 in case of success, a negative error code otherwise.
+ */
+int i3c_device_setmrl_ccc(struct i3c_device *dev, struct i3c_device_info *info, __be16 read_len,
+ u8 ibi_len)
+{
+ struct i3c_master_controller *master = i3c_dev_get_master(dev->desc);
+ int ret = -EINVAL;
+
+ i3c_bus_normaluse_lock(dev->bus);
+ if (master)
+ ret = i3c_master_setmrl_locked(master, info, read_len, ibi_len);
+ i3c_bus_normaluse_unlock(dev->bus);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(i3c_device_setmrl_ccc);
+
+/**
+ * i3c_device_setmwl_ccc() - set maximum write length
+ *
+ * @dev: I3C device to set the length for
+ * @info: I3C device info to fill the length in
+ * @write_len: maximum write length value to be set
+ *
+ * Set I3C device maximum write length from I3C master device via corresponding CCC command
+ *
+ * Return: 0 in case of success, a negative error code otherwise.
+ */
+int i3c_device_setmwl_ccc(struct i3c_device *dev, struct i3c_device_info *info, __be16 write_len)
+{
+ struct i3c_master_controller *master = i3c_dev_get_master(dev->desc);
+ int ret = -EINVAL;
+
+ i3c_bus_normaluse_lock(dev->bus);
+ if (master)
+ ret = i3c_master_setmwl_locked(master, info, write_len);
+ i3c_bus_normaluse_unlock(dev->bus);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(i3c_device_setmwl_ccc);
+
+/**
+ * i3c_device_getmrl_ccc() - get maximum read length
+ *
+ * @dev: I3C device to get the length for
+ * @info: I3C device info to fill the length in
+ *
+ * Receive I3C device maximum read length from I3C master device via corresponding CCC command
+ *
+ * Return: 0 in case of success, a negative error code otherwise.
+ */
+int i3c_device_getmrl_ccc(struct i3c_device *dev, struct i3c_device_info *info)
+{
+ struct i3c_master_controller *master = i3c_dev_get_master(dev->desc);
+ int ret = -EINVAL;
+
+ i3c_bus_normaluse_lock(dev->bus);
+ if (master)
+ ret = i3c_master_getmrl_locked(master, info);
+ i3c_bus_normaluse_unlock(dev->bus);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(i3c_device_getmrl_ccc);
+
+/**
+ * i3c_device_getmwl_ccc() - get maximum write length
+ *
+ * @dev: I3C device to get the length for
+ * @info: I3C device info to fill the length in
+ *
+ * Receive I3C device maximum write length from I3C master device via corresponding CCC command
+ *
+ * Return: 0 in case of success, a negative error code otherwise.
+ */
+int i3c_device_getmwl_ccc(struct i3c_device *dev, struct i3c_device_info *info)
+{
+ struct i3c_master_controller *master = i3c_dev_get_master(dev->desc);
+ int ret = -EINVAL;
+
+ i3c_bus_normaluse_lock(dev->bus);
+ if (master)
+ ret = i3c_master_getmwl_locked(master, info);
+ i3c_bus_normaluse_unlock(dev->bus);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(i3c_device_getmwl_ccc);
diff --git a/drivers/i3c/i3c-hub.c b/drivers/i3c/i3c-hub.c
new file mode 100644
index 000000000000..27b792a962e7
--- /dev/null
+++ b/drivers/i3c/i3c-hub.c
@@ -0,0 +1,708 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2021 Intel Corporation.*/
+
+#include <linux/bitfield.h>
+#include <linux/debugfs.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#include <linux/i3c/device.h>
+#include <linux/i3c/master.h>
+
+#define I3C_HUB_TP_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 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 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 I3C_HUB_CP_MUX_STS 0x39
+
+/* 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
+
+/* Special Function Registers */
+#define I3C_HUB_LDO_AND_CPSEL_STS 0x79
+#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 DT settings */
+#define I3C_HUB_DT_LDO_DISABLED 0x00
+#define I3C_HUB_DT_LDO_1_0V 0x01
+#define I3C_HUB_DT_LDO_1_1V 0x02
+#define I3C_HUB_DT_LDO_1_2V 0x03
+#define I3C_HUB_DT_LDO_1_8V 0x04
+#define I3C_HUB_DT_LDO_NOT_DEFINED 0xFF
+
+/* 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_I3C_PERF 0x02
+#define I3C_HUB_DT_TP_MODE_SMBUS 0x03
+#define I3C_HUB_DT_TP_MODE_GPIO 0x04
+#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
+
+struct tp_setting {
+ u8 mode;
+ u8 pullup_en;
+};
+
+struct dt_settings {
+ u8 cp0_ldo;
+ u8 cp1_ldo;
+ u8 tp0145_ldo;
+ u8 tp2367_ldo;
+ u8 tp0145_pullup;
+ u8 tp2367_pullup;
+ struct tp_setting tp[I3C_HUB_TP_MAX_COUNT];
+};
+
+struct i3c_hub {
+ struct i3c_device *i3cdev;
+ struct regmap *regmap;
+ struct dt_settings settings;
+
+ /* 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_settings[] = {
+ {"disabled", I3C_HUB_DT_LDO_DISABLED},
+ {"1.0V", I3C_HUB_DT_LDO_1_0V},
+ {"1.1V", I3C_HUB_DT_LDO_1_1V},
+ {"1.2V", I3C_HUB_DT_LDO_1_2V},
+ {"1.8V", I3C_HUB_DT_LDO_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},
+ {"i3c-perf", I3C_HUB_DT_TP_MODE_I3C_PERF},
+ {"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 u8 i3c_hub_ldo_dt_to_reg(u8 dt_value)
+{
+ switch (dt_value) {
+ case I3C_HUB_DT_LDO_1_1V:
+ return LDO_VOLTAGE_1_1V;
+ case I3C_HUB_DT_LDO_1_2V:
+ return LDO_VOLTAGE_1_2V;
+ case I3C_HUB_DT_LDO_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 int i3c_hub_of_get_setting(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)
+ return ret;
+
+ 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 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+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;
+ int id;
+
+ for_each_available_child_of_node(node, tp_node) {
+ int ret;
+
+ 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@%i", &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;
+ }
+ ret = i3c_hub_of_get_setting(tp_node, "mode", tp_mode_settings,
+ ARRAY_SIZE(tp_mode_settings), &tp_setting[id].mode);
+ if (ret)
+ dev_warn(dev, "Invalid or not specified setting for target port[%i].mode\n",
+ id);
+
+ ret = i3c_hub_of_get_setting(tp_node, "pullup", tp_pullup_settings,
+ ARRAY_SIZE(tp_pullup_settings),
+ &tp_setting[id].pullup_en);
+ if (ret)
+ dev_warn(dev,
+ "Invalid or not specified setting for target port[%i].pullup\n",
+ id);
+ }
+}
+
+static void i3c_hub_of_get_configuration(struct device *dev, const struct device_node *node)
+{
+ struct i3c_hub *priv = dev_get_drvdata(dev);
+ int ret;
+
+ ret = i3c_hub_of_get_setting(node, "cp0-ldo", ldo_settings, ARRAY_SIZE(ldo_settings),
+ &priv->settings.cp0_ldo);
+ if (ret)
+ dev_warn(dev, "Invalid or not specified setting for cp0-ldo\n");
+
+ ret = i3c_hub_of_get_setting(node, "cp1-ldo", ldo_settings, ARRAY_SIZE(ldo_settings),
+ &priv->settings.cp1_ldo);
+ if (ret)
+ dev_warn(dev, "Invalid or not specified setting for cp1-ldo\n");
+
+ ret = i3c_hub_of_get_setting(node, "tp0145-ldo", ldo_settings, ARRAY_SIZE(ldo_settings),
+ &priv->settings.tp0145_ldo);
+ if (ret)
+ dev_warn(dev, "Invalid or not specified setting for tp0145-ldo\n");
+
+ ret = i3c_hub_of_get_setting(node, "tp2367-ldo", ldo_settings, ARRAY_SIZE(ldo_settings),
+ &priv->settings.tp2367_ldo);
+ if (ret)
+ dev_warn(dev, "Invalid or not specified setting for tp2367-ldo\n");
+
+ ret = i3c_hub_of_get_setting(node, "tp0145-pullup", pullup_settings,
+ ARRAY_SIZE(pullup_settings), &priv->settings.tp0145_pullup);
+ if (ret)
+ dev_warn(dev, "Invalid or not specified setting for tp0145-pullup\n");
+
+ ret = i3c_hub_of_get_setting(node, "tp2367-pullup", pullup_settings,
+ ARRAY_SIZE(pullup_settings), &priv->settings.tp2367_pullup);
+ if (ret)
+ dev_warn(dev, "Invalid or not specified setting for tp2367-pullup\n");
+
+ 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 = I3C_HUB_DT_LDO_NOT_DEFINED;
+ priv->settings.cp1_ldo = I3C_HUB_DT_LDO_NOT_DEFINED;
+ priv->settings.tp0145_ldo = I3C_HUB_DT_LDO_NOT_DEFINED;
+ priv->settings.tp2367_ldo = I3C_HUB_DT_LDO_NOT_DEFINED;
+ priv->settings.tp0145_pullup = I3C_HUB_DT_PULLUP_NOT_DEFINED;
+ priv->settings.tp2367_pullup = I3C_HUB_DT_PULLUP_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 mask_all = 0, val_all = 0;
+ u8 ldo_dis = 0, ldo_en = 0;
+ u32 reg_val;
+ u8 val;
+ int ret;
+
+ /* Get LDOs configuration to figure out what is going to be changed */
+ ret = regmap_read(priv->regmap, I3C_HUB_LDO_CONF, &reg_val);
+ if (ret)
+ return ret;
+
+ if (priv->settings.cp0_ldo != I3C_HUB_DT_LDO_NOT_DEFINED) {
+ val = CP0_LDO_VOLTAGE(i3c_hub_ldo_dt_to_reg(priv->settings.cp0_ldo));
+ if ((reg_val & CP0_LDO_VOLTAGE_MASK) != val)
+ ldo_dis |= CP0_LDO_EN;
+ if (priv->settings.cp0_ldo != I3C_HUB_DT_LDO_DISABLED)
+ ldo_en |= CP0_LDO_EN;
+ mask_all |= CP0_LDO_VOLTAGE_MASK;
+ val_all |= val;
+ }
+ if (priv->settings.cp1_ldo != I3C_HUB_DT_LDO_NOT_DEFINED) {
+ val = CP1_LDO_VOLTAGE(i3c_hub_ldo_dt_to_reg(priv->settings.cp1_ldo));
+ if ((reg_val & CP1_LDO_VOLTAGE_MASK) != val)
+ ldo_dis |= CP1_LDO_EN;
+ if (priv->settings.cp1_ldo != I3C_HUB_DT_LDO_DISABLED)
+ ldo_en |= CP1_LDO_EN;
+ mask_all |= CP1_LDO_VOLTAGE_MASK;
+ val_all |= val;
+ }
+ if (priv->settings.tp0145_ldo != I3C_HUB_DT_LDO_NOT_DEFINED) {
+ val = TP0145_LDO_VOLTAGE(i3c_hub_ldo_dt_to_reg(priv->settings.tp0145_ldo));
+ if ((reg_val & TP0145_LDO_VOLTAGE_MASK) != val)
+ ldo_dis |= TP0145_LDO_EN;
+ if (priv->settings.tp0145_ldo != I3C_HUB_DT_LDO_DISABLED)
+ ldo_en |= TP0145_LDO_EN;
+ mask_all |= TP0145_LDO_VOLTAGE_MASK;
+ val_all |= val;
+ }
+ if (priv->settings.tp2367_ldo != I3C_HUB_DT_LDO_NOT_DEFINED) {
+ val = TP2367_LDO_VOLTAGE(i3c_hub_ldo_dt_to_reg(priv->settings.tp2367_ldo));
+ if ((reg_val & TP2367_LDO_VOLTAGE_MASK) != val)
+ ldo_dis |= TP2367_LDO_EN;
+ if (priv->settings.tp2367_ldo != I3C_HUB_DT_LDO_DISABLED)
+ ldo_en |= TP2367_LDO_EN;
+ mask_all |= TP2367_LDO_VOLTAGE_MASK;
+ val_all |= val;
+ }
+
+ /* Disable all LDOs if LDO configuration is going to be changed. */
+ ret = regmap_update_bits(priv->regmap, I3C_HUB_LDO_AND_PULLUP_CONF, ldo_dis, 0);
+ if (ret)
+ return ret;
+
+ /* Set LDOs configuration */
+ ret = regmap_update_bits(priv->regmap, I3C_HUB_LDO_CONF, mask_all, val_all);
+ if (ret)
+ return ret;
+
+ /* Re-enable LDOs if needed */
+ return regmap_update_bits(priv->regmap, I3C_HUB_LDO_AND_PULLUP_CONF, ldo_en, ldo_en);
+}
+
+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_NET_CON_CONF, i3c_mask, i3c_val);
+ if (ret)
+ return ret;
+
+ /* Set Open-Drain / Push-Pull compatible for I3C mode */
+ ret = regmap_update_bits(priv->regmap, I3C_HUB_TP_IO_MODE_CONF, i3c_mask, ~i3c_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;
+
+ /* 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_PULLUP_EN, pullup_mask, pullup_val);
+}
+
+static int i3c_hub_configure_hw(struct device *dev)
+{
+ int ret;
+
+ ret = i3c_hub_hw_configure_pullup(dev);
+ if (ret)
+ return ret;
+
+ ret = i3c_hub_hw_configure_ldo(dev);
+ if (ret)
+ return ret;
+
+ return i3c_hub_hw_configure_tp(dev);
+}
+
+static const struct i3c_device_id i3c_hub_ids[] = {
+ I3C_CLASS(I3C_DCR_HUB, NULL),
+ { },
+};
+
+static int fops_access_reg_get(void *ctx, u64 *val)
+{
+ struct i3c_hub *priv = ctx;
+ u32 reg_val;
+ int ret;
+
+ ret = regmap_read(priv->regmap, priv->reg_addr, &reg_val);
+ if (ret)
+ return ret;
+
+ *val = reg_val & 0xFF;
+ return 0;
+}
+
+static int fops_access_reg_set(void *ctx, u64 val)
+{
+ struct i3c_hub *priv = ctx;
+
+ return regmap_write(priv->regmap, priv->reg_addr, val & 0xFF);
+}
+DEFINE_DEBUGFS_ATTRIBUTE(fops_access_reg, fops_access_reg_get, fops_access_reg_set, "0x%llX\n");
+
+static int i3c_hub_debugfs_init(struct i3c_hub *priv, const char *hub_id)
+{
+ struct dentry *entry, *dt_conf_dir, *reg_dir;
+ 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;
+
+ debugfs_create_u8("cp0-ldo", 0400, dt_conf_dir, &priv->settings.cp0_ldo);
+ debugfs_create_u8("cp1-ldo", 0400, dt_conf_dir, &priv->settings.cp1_ldo);
+ debugfs_create_u8("tp0145-ldo", 0400, dt_conf_dir, &priv->settings.tp0145_ldo);
+ debugfs_create_u8("tp2367-ldo", 0400, dt_conf_dir, &priv->settings.tp2367_ldo);
+ debugfs_create_u8("tp0145-pullup", 0400, dt_conf_dir, &priv->settings.tp0145_pullup);
+ debugfs_create_u8("tp2367-pullup", 0400, dt_conf_dir, &priv->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, &priv->settings.tp[i].mode);
+ sprintf(file_name, "tp%i.pullup_en", i);
+ debugfs_create_u8(file_name, 0400, dt_conf_dir, &priv->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 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;
+ struct regmap *regmap;
+ struct i3c_hub *priv;
+ char hub_id[32];
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->i3cdev = i3cdev;
+ i3cdev_set_drvdata(i3cdev, priv);
+
+ sprintf(hub_id, "i3c-hub-%d-%llx", i3cdev->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);
+
+ /* TBD: Support for multiple HUBs. */
+ /* Just get first hub node from DT */
+ node = of_get_child_by_name(dev->parent->of_node, "hub");
+ if (!node) {
+ dev_warn(dev, "Failed to find DT entry for the driver. Running with defaults.\n");
+ } else {
+ i3c_hub_of_get_configuration(dev, node);
+ of_node_put(node);
+ }
+
+ 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;
+
+ /* 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;
+ }
+
+ 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;
+ }
+
+ if (i3cdev->bus->jesd403) {
+ i3c_device_send_ccc_cmd(i3cdev, I3C_CCC_SETHID);
+ i3c_device_send_ccc_cmd(i3cdev, I3C_CCC_SETAASA);
+ }
+ /* TBD: Apply special/security lock here using DEV_CMD register */
+
+ 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);
+
+ 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_DESCRIPTION("I3C HUB driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/i3c/i3c-npcm-bic.c b/drivers/i3c/i3c-npcm-bic.c
new file mode 100644
index 000000000000..bb3bf0ad0990
--- /dev/null
+++ b/drivers/i3c/i3c-npcm-bic.c
@@ -0,0 +1,237 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for NPCM Bridge IC.
+ *
+ * Copyright (C) 2023 Nuvoton Technologies
+ */
+
+#include <linux/delay.h>
+#include <linux/i3c/device.h>
+#include <linux/i3c/master.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/sysfs.h>
+#include "internals.h"
+
+#define MQ_BUF_SIZE 8 /* HW limitation that only 8 bytes IBI data can be received */
+#define MQ_BUF_COUNT 64
+#define MQ_BUF_NEXT(x) (((x) + 1) % MQ_BUF_COUNT)
+
+/* If want to use I3C hub to emulate BIC for IBI generation, set HUB_TEST as 1 */
+#define HUB_TEST 0
+
+struct mq_buf {
+ int len;
+ u8 *buf;
+};
+
+struct npcm_bic {
+ struct bin_attribute bin;
+ struct kernfs_node *kn;
+ struct i3c_device *i3cdev;
+ spinlock_t lock;
+ struct mutex mq_lock;
+ int head;
+ int tail;
+ struct mq_buf queue[MQ_BUF_COUNT];
+};
+
+static void i3c_ibi_mqueue_callback(struct i3c_device *dev,
+ const struct i3c_ibi_payload *payload)
+{
+ struct npcm_bic *bic = dev_get_drvdata(&dev->dev);
+ struct mq_buf *mq_buf;
+ u8 *data = (u8 *)payload->data;
+ struct i3c_device_info info;
+
+ dev_dbg(&dev->dev, "%s: payload len %d, queue to mq[%d]\n", __func__,
+ payload->len, bic->tail);
+
+ mutex_lock(&bic->mq_lock);
+ i3c_device_get_info(dev, &info);
+
+ mq_buf = &bic->queue[bic->tail];
+ memset(mq_buf->buf, 0, MQ_BUF_SIZE);
+ mq_buf->len = 0;
+
+ memcpy(mq_buf->buf, data, payload->len);
+ mq_buf->len += payload->len;
+ bic->tail = MQ_BUF_NEXT(bic->tail);
+
+ kernfs_notify(bic->kn);
+ mutex_unlock(&bic->mq_lock);
+}
+
+static ssize_t i3c_npcm_bic_bin_read(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *attr, char *buf,
+ loff_t pos, size_t count)
+{
+ struct npcm_bic *bic;
+ struct mq_buf *mq_buf;
+ unsigned long flags;
+ bool more = false;
+ int i;
+
+ bic = dev_get_drvdata(container_of(kobj, struct device, kobj));
+ spin_lock_irqsave(&bic->lock, flags);
+ if (bic->head != bic->tail) {
+ mq_buf = &bic->queue[bic->head];
+ memcpy(buf, mq_buf->buf, mq_buf->len);
+
+ dev_dbg(&bic->i3cdev->dev, "mq[%d] =\n", bic->head);
+ for(i = 0; i < mq_buf->len; i++)
+ dev_dbg(&bic->i3cdev->dev, "%02x ", buf[i]);
+ dev_dbg(&bic->i3cdev->dev, "\n\n");
+
+ bic->head = MQ_BUF_NEXT(bic->head);
+ if (bic->head != bic->tail)
+ more = true;
+ }
+ spin_unlock_irqrestore(&bic->lock, flags);
+
+ if (more)
+ kernfs_notify(bic->kn);
+
+ return 0;
+}
+
+static ssize_t i3c_npcm_bic_bin_write(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *attr, char *buf,
+ loff_t pos, size_t count)
+{
+ struct npcm_bic *bic;
+ struct i3c_priv_xfer xfers = {
+ .rnw = false,
+ .len = count,
+ };
+ int ret;
+
+ bic = dev_get_drvdata(container_of(kobj, struct device, kobj));
+
+ dev_dbg(&bic->i3cdev->dev, "%s: count = %zu\n", __func__, count);
+ xfers.data.out = buf;
+ ret = i3c_device_do_priv_xfers(bic->i3cdev, &xfers, 1);
+
+ return (!ret) ? count : ret;
+}
+
+static void i3c_npcm_bic_remove(struct i3c_device *i3cdev)
+{
+ struct device *dev = &i3cdev->dev;
+ struct npcm_bic *bic;
+
+ i3c_device_disable_ibi(i3cdev);
+ i3c_device_free_ibi(i3cdev);
+
+ bic = dev_get_drvdata(dev);
+ kernfs_put(bic->kn);
+ sysfs_remove_bin_file(&dev->kobj, &bic->bin);
+ devm_kfree(dev, bic);
+}
+
+static int i3c_npcm_bic_probe(struct i3c_device *i3cdev)
+{
+ struct device *dev = &i3cdev->dev;
+ struct npcm_bic *bic;
+ struct i3c_ibi_setup ibireq = {};
+ int ret, i;
+ struct i3c_device_info info;
+ void *buf;
+
+ if (dev->type == &i3c_masterdev_type)
+ return -ENOTSUPP;
+
+ bic = devm_kzalloc(dev, sizeof(*bic), GFP_KERNEL);
+ if (!bic)
+ return -ENOMEM;
+
+ buf = devm_kmalloc_array(dev, MQ_BUF_COUNT, MQ_BUF_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ for (i = 0; i < MQ_BUF_COUNT; i++) {
+ bic->queue[i].buf = (u8 *)buf + i * MQ_BUF_SIZE;
+ bic->queue[i].len = 0;
+ }
+
+ bic->head = 0;
+ bic->tail = 0;
+ bic->i3cdev = i3cdev;
+ bic->bin.attr.name = "mqueue";
+ bic->bin.attr.mode = 0600;
+ bic->bin.read = i3c_npcm_bic_bin_read;
+ bic->bin.write = i3c_npcm_bic_bin_write;
+ bic->bin.size = MQ_BUF_SIZE * MQ_BUF_COUNT;
+
+ spin_lock_init(&bic->lock);
+ mutex_init(&bic->mq_lock);
+
+ sysfs_bin_attr_init(&bic->bin);
+ ret = sysfs_create_bin_file(&dev->kobj, &bic->bin);
+ if (ret) {
+ dev_err(dev, "Failed to create bin file\n");
+ return ret;
+ }
+
+ bic->kn = kernfs_find_and_get(dev->kobj.sd, bic->bin.attr.name);
+ if (!bic->kn) {
+ ret = -EFAULT;
+ goto rel_bin_file;
+ }
+
+ i3c_device_get_info(i3cdev, &info);
+ ret = i3c_device_setmrl_ccc(i3cdev, &info, MQ_BUF_SIZE,
+ min(MQ_BUF_SIZE, __UINT8_MAX__));
+ if (ret) {
+ dev_err(dev, "Failed to SETMRL\n");
+#if !HUB_TEST
+ goto rel_kn;
+#endif
+ }
+
+ dev_set_drvdata(dev, bic);
+
+ ibireq.handler = i3c_ibi_mqueue_callback;
+ ibireq.max_payload_len = MQ_BUF_SIZE;
+ ibireq.num_slots = MQ_BUF_COUNT;
+
+ ret = i3c_device_request_ibi(bic->i3cdev, &ibireq);
+ ret |= i3c_device_enable_ibi(bic->i3cdev);
+ if (ret) {
+ dev_err(dev, "Failed to request and enable IBI\n");
+ goto rel_kn;
+ }
+
+ return 0;
+
+rel_kn:
+ kernfs_put(bic->kn);
+rel_bin_file:
+ sysfs_remove_bin_file(&dev->kobj, &bic->bin);
+
+ return ret;
+}
+
+static const struct i3c_device_id i3c_npcm_bic_ids[] = {
+ /* TODO: add ids for matching BIC here */
+#if HUB_TEST
+ I3C_CLASS(0xc2, NULL),
+#endif
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(i3c, i3c_npcm_bic_ids);
+
+static struct i3c_driver npcm_bic_driver = {
+ .driver = {
+ .name = "i3c-npcm-bic",
+ },
+ .probe = i3c_npcm_bic_probe,
+ .remove = i3c_npcm_bic_remove,
+ .id_table = i3c_npcm_bic_ids,
+};
+module_i3c_driver(npcm_bic_driver);
+
+MODULE_AUTHOR("Marvin Lin <kflin@nuvoton.com>");
+MODULE_DESCRIPTION("Driver for NPCM Bridge IC");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/i3c/i3cdev.c b/drivers/i3c/i3cdev.c
new file mode 100644
index 000000000000..ed16125fc758
--- /dev/null
+++ b/drivers/i3c/i3cdev.c
@@ -0,0 +1,435 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2019 Synopsys, Inc. and/or its affiliates.
+ *
+ * Author: Vitor Soares <soares@synopsys.com>
+ */
+
+#include <linux/cdev.h>
+#include <linux/compat.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include <linux/i3c/i3cdev.h>
+
+#include "internals.h"
+
+struct i3cdev_data {
+ struct list_head list;
+ struct i3c_device *i3c;
+ struct cdev cdev;
+ struct device *dev;
+ int id;
+};
+
+static DEFINE_IDA(i3cdev_ida);
+static dev_t i3cdev_number;
+#define I3C_MINORS 32 /* 32 I3C devices supported for now */
+
+static LIST_HEAD(i3cdev_list);
+static DEFINE_SPINLOCK(i3cdev_list_lock);
+
+static struct i3cdev_data *i3cdev_get_by_i3c(struct i3c_device *i3c)
+{
+ struct i3cdev_data *i3cdev;
+
+ spin_lock(&i3cdev_list_lock);
+ list_for_each_entry(i3cdev, &i3cdev_list, list) {
+ if (i3cdev->i3c == i3c)
+ goto found;
+ }
+
+ i3cdev = NULL;
+
+found:
+ spin_unlock(&i3cdev_list_lock);
+ return i3cdev;
+}
+
+static struct i3cdev_data *get_free_i3cdev(struct i3c_device *i3c)
+{
+ struct i3cdev_data *i3cdev;
+ int id;
+
+ id = ida_simple_get(&i3cdev_ida, 0, I3C_MINORS, GFP_KERNEL);
+ if (id < 0) {
+ pr_err("i3cdev: no minor number available!\n");
+ return ERR_PTR(id);
+ }
+
+ i3cdev = kzalloc(sizeof(*i3cdev), GFP_KERNEL);
+ if (!i3cdev) {
+ ida_simple_remove(&i3cdev_ida, id);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ i3cdev->i3c = i3c;
+ i3cdev->id = id;
+
+ spin_lock(&i3cdev_list_lock);
+ list_add_tail(&i3cdev->list, &i3cdev_list);
+ spin_unlock(&i3cdev_list_lock);
+
+ return i3cdev;
+}
+
+static void put_i3cdev(struct i3cdev_data *i3cdev)
+{
+ spin_lock(&i3cdev_list_lock);
+ list_del(&i3cdev->list);
+ spin_unlock(&i3cdev_list_lock);
+ kfree(i3cdev);
+}
+
+static ssize_t
+i3cdev_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos)
+{
+ struct i3c_device *i3c = file->private_data;
+ struct i3c_priv_xfer xfers = {
+ .rnw = true,
+ .len = count,
+ };
+ char *tmp;
+ int ret;
+
+ tmp = kzalloc(count, GFP_KERNEL);
+ if (!tmp)
+ return -ENOMEM;
+
+ xfers.data.in = tmp;
+
+ dev_dbg(&i3c->dev, "Reading %zu bytes.\n", count);
+
+ ret = i3c_device_do_priv_xfers(i3c, &xfers, 1);
+ if (!ret)
+ ret = copy_to_user(buf, tmp, count) ? -EFAULT : ret;
+
+ kfree(tmp);
+ return ret;
+}
+
+static ssize_t
+i3cdev_write(struct file *file, const char __user *buf, size_t count,
+ loff_t *f_pos)
+{
+ struct i3c_device *i3c = file->private_data;
+ struct i3c_priv_xfer xfers = {
+ .rnw = false,
+ .len = count,
+ };
+ char *tmp;
+ int ret;
+
+ tmp = memdup_user(buf, count);
+ if (IS_ERR(tmp))
+ return PTR_ERR(tmp);
+
+ xfers.data.out = tmp;
+
+ dev_dbg(&i3c->dev, "Writing %zu bytes.\n", count);
+
+ ret = i3c_device_do_priv_xfers(i3c, &xfers, 1);
+ kfree(tmp);
+ return (!ret) ? count : ret;
+}
+
+static int
+i3cdev_do_priv_xfer(struct i3c_device *dev, struct i3c_ioc_priv_xfer *xfers,
+ unsigned int nxfers)
+{
+ struct i3c_priv_xfer *k_xfers;
+ u8 **data_ptrs;
+ int i, ret = 0;
+
+ k_xfers = kcalloc(nxfers, sizeof(*k_xfers), GFP_KERNEL);
+ if (!k_xfers)
+ return -ENOMEM;
+
+ data_ptrs = kcalloc(nxfers, sizeof(*data_ptrs), GFP_KERNEL);
+ if (!data_ptrs) {
+ ret = -ENOMEM;
+ goto err_free_k_xfer;
+ }
+
+ for (i = 0; i < nxfers; i++) {
+ data_ptrs[i] = memdup_user((const u8 __user *)
+ (uintptr_t)xfers[i].data,
+ xfers[i].len);
+ if (IS_ERR(data_ptrs[i])) {
+ ret = PTR_ERR(data_ptrs[i]);
+ break;
+ }
+
+ k_xfers[i].len = xfers[i].len;
+ if (xfers[i].rnw) {
+ k_xfers[i].rnw = true;
+ k_xfers[i].data.in = data_ptrs[i];
+ } else {
+ k_xfers[i].rnw = false;
+ k_xfers[i].data.out = data_ptrs[i];
+ }
+ }
+
+ if (ret < 0) {
+ nxfers = i;
+ goto err_free_mem;
+ }
+
+ ret = i3c_device_do_priv_xfers(dev, k_xfers, nxfers);
+ if (ret)
+ goto err_free_mem;
+
+ for (i = 0; i < nxfers; i++) {
+ if (xfers[i].rnw) {
+ if (copy_to_user((void __user *)(uintptr_t)xfers[i].data,
+ data_ptrs[i], xfers[i].len))
+ ret = -EFAULT;
+ xfers[i].len = k_xfers[i].len;
+ }
+ }
+
+err_free_mem:
+ for (i = 0; i < nxfers; i++)
+ kfree(data_ptrs[i]);
+ kfree(data_ptrs);
+err_free_k_xfer:
+ kfree(k_xfers);
+ return ret;
+}
+
+static struct i3c_ioc_priv_xfer *
+i3cdev_get_ioc_priv_xfer(unsigned int cmd, struct i3c_ioc_priv_xfer *u_xfers,
+ unsigned int *nxfers)
+{
+ u32 tmp = _IOC_SIZE(cmd);
+
+ if ((tmp % sizeof(struct i3c_ioc_priv_xfer)) != 0)
+ return ERR_PTR(-EINVAL);
+
+ *nxfers = tmp / sizeof(struct i3c_ioc_priv_xfer);
+ if (*nxfers == 0)
+ return NULL;
+
+ return memdup_user(u_xfers, tmp);
+}
+
+static int
+i3cdev_ioc_priv_xfer(struct i3c_device *i3c, unsigned int cmd,
+ struct i3c_ioc_priv_xfer *u_xfers)
+{
+ struct i3c_ioc_priv_xfer *k_xfers;
+ unsigned int nxfers;
+ int ret, i;
+
+ k_xfers = i3cdev_get_ioc_priv_xfer(cmd, u_xfers, &nxfers);
+ if (IS_ERR_OR_NULL(k_xfers))
+ return PTR_ERR(k_xfers);
+
+ ret = i3cdev_do_priv_xfer(i3c, k_xfers, nxfers);
+
+ for (i = 0; i < nxfers; i++) {
+ if (k_xfers[i].rnw)
+ put_user(k_xfers[i].len, &u_xfers[i].len);
+ }
+
+ kfree(k_xfers);
+
+ return ret;
+}
+
+static long
+i3cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct i3c_device *i3c = file->private_data;
+
+ dev_dbg(&i3c->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n", cmd, arg);
+
+ if (_IOC_TYPE(cmd) != I3C_DEV_IOC_MAGIC)
+ return -ENOTTY;
+
+ /* Check command number and direction */
+ if (_IOC_NR(cmd) == _IOC_NR(I3C_IOC_PRIV_XFER(0)) &&
+ _IOC_DIR(cmd) == (_IOC_READ | _IOC_WRITE))
+ return i3cdev_ioc_priv_xfer(i3c, cmd,
+ (struct i3c_ioc_priv_xfer __user *)arg);
+
+ return 0;
+}
+
+static int i3cdev_open(struct inode *inode, struct file *file)
+{
+ struct i3cdev_data *i3cdev = container_of(inode->i_cdev,
+ struct i3cdev_data,
+ cdev);
+
+ file->private_data = i3cdev->i3c;
+
+ return 0;
+}
+
+static int i3cdev_release(struct inode *inode, struct file *file)
+{
+ file->private_data = NULL;
+
+ return 0;
+}
+
+static const struct file_operations i3cdev_fops = {
+ .owner = THIS_MODULE,
+ .read = i3cdev_read,
+ .write = i3cdev_write,
+ .unlocked_ioctl = i3cdev_ioctl,
+ .open = i3cdev_open,
+ .release = i3cdev_release,
+};
+
+/* ------------------------------------------------------------------------- */
+
+static struct class *i3cdev_class;
+
+static int i3cdev_attach(struct device *dev, void *dummy)
+{
+ struct i3cdev_data *i3cdev;
+ struct i3c_device *i3c;
+ int res;
+
+ if (dev->type == &i3c_masterdev_type || dev->driver)
+ return 0;
+
+ i3c = dev_to_i3cdev(dev);
+
+ /* Get a device */
+ i3cdev = get_free_i3cdev(i3c);
+ if (IS_ERR(i3cdev))
+ return PTR_ERR(i3cdev);
+
+ cdev_init(&i3cdev->cdev, &i3cdev_fops);
+ i3cdev->cdev.owner = THIS_MODULE;
+ res = cdev_add(&i3cdev->cdev,
+ MKDEV(MAJOR(i3cdev_number), i3cdev->id), 1);
+ if (res)
+ goto error_cdev;
+
+ /* register this i3c device with the driver core */
+ i3cdev->dev = device_create(i3cdev_class, &i3c->dev,
+ MKDEV(MAJOR(i3cdev_number), i3cdev->id),
+ NULL, "i3c-%s", dev_name(&i3c->dev));
+ if (IS_ERR(i3cdev->dev)) {
+ res = PTR_ERR(i3cdev->dev);
+ goto error;
+ }
+ pr_debug("i3cdev: I3C device [%s] registered as minor %d\n",
+ dev_name(&i3c->dev), i3cdev->id);
+ return 0;
+
+error:
+ cdev_del(&i3cdev->cdev);
+error_cdev:
+ put_i3cdev(i3cdev);
+ return res;
+}
+
+static int i3cdev_detach(struct device *dev, void *dummy)
+{
+ struct i3cdev_data *i3cdev;
+ struct i3c_device *i3c;
+
+ if (dev->type == &i3c_masterdev_type)
+ return 0;
+
+ i3c = dev_to_i3cdev(dev);
+
+ i3cdev = i3cdev_get_by_i3c(i3c);
+ if (!i3cdev)
+ return 0;
+
+ cdev_del(&i3cdev->cdev);
+ device_destroy(i3cdev_class, MKDEV(MAJOR(i3cdev_number), i3cdev->id));
+ ida_simple_remove(&i3cdev_ida, i3cdev->id);
+ put_i3cdev(i3cdev);
+
+ pr_debug("i3cdev: device [%s] unregistered\n", dev_name(&i3c->dev));
+
+ return 0;
+}
+
+static int i3cdev_notifier_call(struct notifier_block *nb,
+ unsigned long action,
+ void *data)
+{
+ struct device *dev = data;
+
+ switch (action) {
+ case BUS_NOTIFY_ADD_DEVICE:
+ case BUS_NOTIFY_UNBOUND_DRIVER:
+ return i3cdev_attach(dev, NULL);
+ case BUS_NOTIFY_DEL_DEVICE:
+ case BUS_NOTIFY_BOUND_DRIVER:
+ case BUS_NOTIFY_REMOVED_DEVICE:
+ return i3cdev_detach(dev, NULL);
+ }
+
+ return 0;
+}
+
+static struct notifier_block i3c_notifier = {
+ .notifier_call = i3cdev_notifier_call,
+};
+
+static int __init i3cdev_init(void)
+{
+ int res;
+
+ /* Dynamically request unused major number */
+ res = alloc_chrdev_region(&i3cdev_number, 0, I3C_MINORS, "i3c");
+ if (res)
+ goto out;
+
+ /* Create a classe to populate sysfs entries*/
+ i3cdev_class = class_create(THIS_MODULE, "i3cdev");
+ if (IS_ERR(i3cdev_class)) {
+ res = PTR_ERR(i3cdev_class);
+ goto out_unreg_chrdev;
+ }
+
+ /* Keep track of busses which have devices to add or remove later */
+ res = bus_register_notifier(&i3c_bus_type, &i3c_notifier);
+ if (res)
+ goto out_unreg_class;
+
+ /* Bind to already existing device without driver right away */
+ i3c_for_each_dev(NULL, i3cdev_attach);
+
+ return 0;
+
+out_unreg_class:
+ class_destroy(i3cdev_class);
+out_unreg_chrdev:
+ unregister_chrdev_region(i3cdev_number, I3C_MINORS);
+out:
+ pr_err("%s: Driver Initialisation failed\n", __FILE__);
+ return res;
+}
+
+static void __exit i3cdev_exit(void)
+{
+ bus_unregister_notifier(&i3c_bus_type, &i3c_notifier);
+ i3c_for_each_dev(NULL, i3cdev_detach);
+ class_destroy(i3cdev_class);
+ unregister_chrdev_region(i3cdev_number, I3C_MINORS);
+}
+
+MODULE_AUTHOR("Vitor Soares <soares@synopsys.com>");
+MODULE_DESCRIPTION("I3C /dev entries driver");
+MODULE_LICENSE("GPL");
+
+module_init(i3cdev_init);
+module_exit(i3cdev_exit);
diff --git a/drivers/i3c/internals.h b/drivers/i3c/internals.h
index 86b7b44cfca2..b34c4460c0b6 100644
--- a/drivers/i3c/internals.h
+++ b/drivers/i3c/internals.h
@@ -9,8 +9,10 @@
#define I3C_INTERNALS_H
#include <linux/i3c/master.h>
+#include <linux/i3c/target.h>
extern struct bus_type i3c_bus_type;
+extern const struct device_type i3c_masterdev_type;
void i3c_bus_normaluse_lock(struct i3c_bus *bus);
void i3c_bus_normaluse_unlock(struct i3c_bus *bus);
@@ -23,4 +25,14 @@ int i3c_dev_enable_ibi_locked(struct i3c_dev_desc *dev);
int i3c_dev_request_ibi_locked(struct i3c_dev_desc *dev,
const struct i3c_ibi_setup *req);
void i3c_dev_free_ibi_locked(struct i3c_dev_desc *dev);
+int i3c_dev_send_ccc_cmd_locked(struct i3c_dev_desc *dev, u8 ccc_id);
+int i3c_dev_getstatus_locked(struct i3c_dev_desc *dev, struct i3c_device_info *info);
+int i3c_master_getmrl_locked(struct i3c_master_controller *master, struct i3c_device_info *info);
+int i3c_master_getmwl_locked(struct i3c_master_controller *master, struct i3c_device_info *info);
+int i3c_master_setmrl_locked(struct i3c_master_controller *master,
+ struct i3c_device_info *info, __be16 read_len, u8 ibi_len);
+int i3c_master_setmwl_locked(struct i3c_master_controller *master,
+ struct i3c_device_info *info, __be16 write_len);
+int i3c_for_each_dev(void *data, int (*fn)(struct device *, void *));
+int i3c_dev_generate_ibi_locked(struct i3c_dev_desc *dev, const u8 *data, int len);
#endif /* I3C_INTERNAL_H */
diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index b6abbb0acbbd..9ade31fbae5f 100644
--- a/drivers/i3c/master.c
+++ b/drivers/i3c/master.c
@@ -21,6 +21,7 @@
static DEFINE_IDR(i3c_bus_idr);
static DEFINE_MUTEX(i3c_core_lock);
+static BLOCKING_NOTIFIER_HEAD(i3c_bus_notifier);
/**
* i3c_bus_maintenance_lock - Lock the bus for a maintenance operation
@@ -298,19 +299,24 @@ static const struct device_type i3c_device_type = {
.uevent = i3c_device_uevent,
};
+const struct device_type i3c_target_device_type = {
+};
+
static int i3c_device_match(struct device *dev, struct device_driver *drv)
{
struct i3c_device *i3cdev;
struct i3c_driver *i3cdrv;
- if (dev->type != &i3c_device_type)
+ if (dev->type != &i3c_device_type && dev->type != &i3c_target_device_type)
return 0;
i3cdev = dev_to_i3cdev(dev);
i3cdrv = drv_to_i3cdrv(drv);
- if (i3c_device_match_id(i3cdev, i3cdrv->id_table))
- return 1;
+ if ((dev->type == &i3c_device_type && !i3cdrv->target) ||
+ (dev->type == &i3c_target_device_type && i3cdrv->target))
+ if (i3c_device_match_id(i3cdev, i3cdrv->id_table))
+ return 1;
return 0;
}
@@ -330,7 +336,8 @@ static void i3c_device_remove(struct device *dev)
if (driver->remove)
driver->remove(i3cdev);
- i3c_device_free_ibi(i3cdev);
+ if (!driver->target)
+ i3c_device_free_ibi(i3cdev);
}
struct bus_type i3c_bus_type = {
@@ -441,6 +448,36 @@ static int i3c_bus_init(struct i3c_bus *i3cbus)
return 0;
}
+void i3c_for_each_bus_locked(int (*fn)(struct i3c_bus *bus, void *data),
+ void *data)
+{
+ struct i3c_bus *bus;
+ int id;
+
+ mutex_lock(&i3c_core_lock);
+ idr_for_each_entry(&i3c_bus_idr, bus, id)
+ fn(bus, data);
+ mutex_unlock(&i3c_core_lock);
+}
+EXPORT_SYMBOL_GPL(i3c_for_each_bus_locked);
+
+int i3c_register_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_register(&i3c_bus_notifier, nb);
+}
+EXPORT_SYMBOL_GPL(i3c_register_notifier);
+
+int i3c_unregister_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_unregister(&i3c_bus_notifier, nb);
+}
+EXPORT_SYMBOL_GPL(i3c_unregister_notifier);
+
+static void i3c_bus_notify(struct i3c_bus *bus, unsigned int action)
+{
+ blocking_notifier_call_chain(&i3c_bus_notifier, action, bus);
+}
+
static const char * const i3c_bus_mode_strings[] = {
[I3C_BUS_MODE_PURE] = "pure",
[I3C_BUS_MODE_MIXED_FAST] = "mixed-fast",
@@ -514,6 +551,20 @@ static ssize_t i2c_scl_frequency_show(struct device *dev,
}
static DEVICE_ATTR_RO(i2c_scl_frequency);
+static ssize_t discover_store(struct device *dev, struct device_attribute *da,
+ const char *buf, size_t count)
+{
+ struct i3c_master_controller *master;
+ ssize_t ret = count;
+
+ master = dev_to_i3cmaster(dev);
+ dev_dbg(&master->dev, "Request master to do DAA\n");
+ i3c_master_do_daa(master);
+
+ return ret;
+}
+static DEVICE_ATTR_WO(discover);
+
static struct attribute *i3c_masterdev_attrs[] = {
&dev_attr_mode.attr,
&dev_attr_current_master.attr,
@@ -524,6 +575,7 @@ static struct attribute *i3c_masterdev_attrs[] = {
&dev_attr_pid.attr,
&dev_attr_dynamic_address.attr,
&dev_attr_hdrcap.attr,
+ &dev_attr_discover.attr,
NULL,
};
ATTRIBUTE_GROUPS(i3c_masterdev);
@@ -542,7 +594,7 @@ static void i3c_masterdev_release(struct device *dev)
of_node_put(dev->of_node);
}
-static const struct device_type i3c_masterdev_type = {
+const struct device_type i3c_masterdev_type = {
.groups = i3c_masterdev_groups,
};
@@ -775,6 +827,20 @@ static int i3c_master_rstdaa_locked(struct i3c_master_controller *master,
return ret;
}
+static int i3c_master_setaasa_locked(struct i3c_master_controller *master)
+{
+ struct i3c_ccc_cmd_dest dest;
+ struct i3c_ccc_cmd cmd;
+ int ret;
+
+ i3c_ccc_cmd_dest_init(&dest, I3C_BROADCAST_ADDR, 0);
+ i3c_ccc_cmd_init(&cmd, false, I3C_CCC_SETAASA, &dest, 1);
+ ret = i3c_master_send_ccc_cmd_locked(master, &cmd);
+ i3c_ccc_cmd_dest_cleanup(&dest);
+
+ return ret;
+}
+
/**
* i3c_master_entdaa_locked() - start a DAA (Dynamic Address Assignment)
* procedure
@@ -1001,8 +1067,27 @@ static int i3c_master_setnewda_locked(struct i3c_master_controller *master,
return i3c_master_setda_locked(master, oldaddr, newaddr, false);
}
-static int i3c_master_getmrl_locked(struct i3c_master_controller *master,
- struct i3c_device_info *info)
+static int i3c_master_sethid_locked(struct i3c_master_controller *master)
+{
+ struct i3c_ccc_cmd_dest dest;
+ struct i3c_ccc_cmd cmd;
+ struct i3c_ccc_sethid *sethid;
+ int ret;
+
+ sethid = i3c_ccc_cmd_dest_init(&dest, I3C_BROADCAST_ADDR, 1);
+ if (!sethid)
+ return -ENOMEM;
+
+ sethid->hid = 0;
+ i3c_ccc_cmd_init(&cmd, false, I3C_CCC_SETHID, &dest, 1);
+
+ ret = i3c_master_send_ccc_cmd_locked(master, &cmd);
+ i3c_ccc_cmd_dest_cleanup(&dest);
+
+ return ret;
+}
+
+int i3c_master_getmrl_locked(struct i3c_master_controller *master, struct i3c_device_info *info)
{
struct i3c_ccc_cmd_dest dest;
struct i3c_ccc_mrl *mrl;
@@ -1043,8 +1128,7 @@ static int i3c_master_getmrl_locked(struct i3c_master_controller *master,
return ret;
}
-static int i3c_master_getmwl_locked(struct i3c_master_controller *master,
- struct i3c_device_info *info)
+int i3c_master_getmwl_locked(struct i3c_master_controller *master, struct i3c_device_info *info)
{
struct i3c_ccc_cmd_dest dest;
struct i3c_ccc_mwl *mwl;
@@ -1073,6 +1157,52 @@ static int i3c_master_getmwl_locked(struct i3c_master_controller *master,
return ret;
}
+int i3c_master_setmrl_locked(struct i3c_master_controller *master,
+ struct i3c_device_info *info, u16 read_len, u8 ibi_len)
+{
+ struct i3c_ccc_cmd_dest dest;
+ struct i3c_ccc_cmd cmd;
+ struct i3c_ccc_mrl *mrl;
+ int ret;
+
+ mrl = i3c_ccc_cmd_dest_init(&dest, info->dyn_addr, sizeof(*mrl));
+ if (!mrl)
+ return -ENOMEM;
+
+ mrl->read_len = cpu_to_be16(read_len);
+ mrl->ibi_len = ibi_len;
+ info->max_read_len = read_len;
+ info->max_ibi_len = mrl->ibi_len;
+ i3c_ccc_cmd_init(&cmd, false, I3C_CCC_SETMRL(false), &dest, 1);
+
+ ret = i3c_master_send_ccc_cmd_locked(master, &cmd);
+ i3c_ccc_cmd_dest_cleanup(&dest);
+
+ return ret;
+}
+
+int i3c_master_setmwl_locked(struct i3c_master_controller *master,
+ struct i3c_device_info *info, u16 write_len)
+{
+ struct i3c_ccc_cmd_dest dest;
+ struct i3c_ccc_cmd cmd;
+ struct i3c_ccc_mwl *mwl;
+ int ret;
+
+ mwl = i3c_ccc_cmd_dest_init(&dest, info->dyn_addr, sizeof(*mwl));
+ if (!mwl)
+ return -ENOMEM;
+
+ mwl->len = cpu_to_be16(write_len);
+ info->max_write_len = write_len;
+ i3c_ccc_cmd_init(&cmd, false, I3C_CCC_SETMWL(false), &dest, 1);
+
+ ret = i3c_master_send_ccc_cmd_locked(master, &cmd);
+ i3c_ccc_cmd_dest_cleanup(&dest);
+
+ return ret;
+}
+
static int i3c_master_getmxds_locked(struct i3c_master_controller *master,
struct i3c_device_info *info)
{
@@ -1220,6 +1350,32 @@ static int i3c_master_getdcr_locked(struct i3c_master_controller *master,
return ret;
}
+int i3c_dev_getstatus_locked(struct i3c_dev_desc *dev,
+ struct i3c_device_info *info)
+{
+ struct i3c_master_controller *master = i3c_dev_get_master(dev);
+ struct i3c_ccc_getstatus *getsts;
+ struct i3c_ccc_cmd_dest dest;
+ struct i3c_ccc_cmd cmd;
+ int ret;
+
+ getsts = i3c_ccc_cmd_dest_init(&dest, info->dyn_addr, sizeof(*getsts));
+ if (!getsts)
+ return -ENOMEM;
+
+ i3c_ccc_cmd_init(&cmd, true, I3C_CCC_GETSTATUS, &dest, 1);
+ ret = i3c_master_send_ccc_cmd_locked(master, &cmd);
+ if (ret)
+ goto out;
+
+ info->status = be16_to_cpu(getsts->status);
+
+out:
+ i3c_ccc_cmd_dest_cleanup(&dest);
+
+ return ret;
+}
+
static int i3c_master_retrieve_dev_info(struct i3c_dev_desc *dev)
{
struct i3c_master_controller *master = i3c_dev_get_master(dev);
@@ -1380,6 +1536,9 @@ static int i3c_master_reattach_i3c_dev(struct i3c_dev_desc *dev,
i3c_bus_set_addr_slot_status(&master->bus,
dev->info.dyn_addr,
I3C_ADDR_SLOT_I3C_DEV);
+ if (old_dyn_addr)
+ i3c_bus_set_addr_slot_status(&master->bus, old_dyn_addr,
+ I3C_ADDR_SLOT_FREE);
}
if (master->ops->reattach_i3c_dev) {
@@ -1431,6 +1590,36 @@ static void i3c_master_detach_i2c_dev(struct i2c_dev_desc *dev)
master->ops->detach_i2c_dev(dev);
}
+static int i3c_master_add_static_i3c_dev(struct i3c_master_controller *master,
+ struct i3c_dev_boardinfo *boardinfo)
+{
+ struct i3c_device_info info = {
+ .static_addr = boardinfo->static_addr,
+ };
+ struct i3c_dev_desc *i3cdev;
+ int ret;
+
+ i3cdev = i3c_master_alloc_i3c_dev(master, &info);
+ if (IS_ERR(i3cdev))
+ return -ENOMEM;
+
+ i3cdev->boardinfo = boardinfo;
+ i3cdev->info.static_addr = boardinfo->static_addr;
+ i3cdev->info.pid = boardinfo->pid;
+ i3cdev->info.dyn_addr = boardinfo->static_addr;
+
+ ret = i3c_master_attach_i3c_dev(master, i3cdev);
+ if (ret)
+ goto err_free_dev;
+
+ return 0;
+
+err_free_dev:
+ i3c_master_free_i3c_dev(i3cdev);
+
+ return ret;
+}
+
static int i3c_master_early_i3c_dev_add(struct i3c_master_controller *master,
struct i3c_dev_boardinfo *boardinfo)
{
@@ -1506,11 +1695,9 @@ i3c_master_register_new_i3c_devs(struct i3c_master_controller *master)
desc->dev->dev.of_node = desc->boardinfo->of_node;
ret = device_register(&desc->dev->dev);
- if (ret) {
+ if (ret)
dev_err(&master->dev,
"Failed to add I3C device (err = %d)\n", ret);
- put_device(&desc->dev->dev);
- }
}
}
@@ -1746,6 +1933,18 @@ static int i3c_master_bus_init(struct i3c_master_controller *master)
* i3c_master_add_i3c_dev_locked().
*/
list_for_each_entry(i3cboardinfo, &master->boardinfo.i3c, node) {
+ if (!i3cboardinfo->init_dyn_addr && master->bus.jesd403) {
+ /* JESD403 devices only support setaasa */
+ ret = i3c_bus_get_addr_slot_status(&master->bus,
+ i3cboardinfo->static_addr);
+ if (ret != I3C_ADDR_SLOT_FREE) {
+ ret = -EBUSY;
+ goto err_rstdaa;
+ }
+ i3cboardinfo->init_dyn_addr = i3cboardinfo->static_addr;
+ i3c_master_add_static_i3c_dev(master, i3cboardinfo);
+ continue;
+ }
/*
* We don't reserve a dynamic address for devices that
@@ -1777,6 +1976,16 @@ static int i3c_master_bus_init(struct i3c_master_controller *master)
i3c_master_early_i3c_dev_add(master, i3cboardinfo);
}
+ if (master->bus.jesd403) {
+ i3c_master_sethid_locked(master);
+ i3c_master_setaasa_locked(master);
+
+ i3c_bus_normaluse_lock(&master->bus);
+ i3c_master_register_new_i3c_devs(master);
+ i3c_bus_normaluse_unlock(&master->bus);
+ return 0;
+ }
+
ret = i3c_master_do_daa(master);
if (ret)
goto err_rstdaa;
@@ -1896,7 +2105,15 @@ int i3c_master_add_i3c_dev_locked(struct i3c_master_controller *master,
if (olddev->ibi->enabled) {
enable_ibi = true;
- i3c_dev_disable_ibi_locked(olddev);
+ ret = i3c_dev_disable_ibi_locked(olddev);
+ /*
+ * If olddev is not active on the bus,
+ * disable_ibi will get NACK.
+ * Set ibi->enabled to false to
+ * avoid warning message in free_ibi.
+ */
+ if (ret == I3C_ERROR_M2)
+ olddev->ibi->enabled = false;
}
i3c_dev_free_ibi_locked(olddev);
@@ -1909,10 +2126,6 @@ int i3c_master_add_i3c_dev_locked(struct i3c_master_controller *master,
i3c_master_free_i3c_dev(olddev);
}
- ret = i3c_master_reattach_i3c_dev(newdev, old_dyn_addr);
- if (ret)
- goto err_detach_dev;
-
/*
* Depending on our previous state, the expected dynamic address might
* differ:
@@ -2128,6 +2341,9 @@ static int of_populate_i3c_bus(struct i3c_master_controller *master)
if (!of_property_read_u32(i3cbus_np, "i3c-scl-hz", &val))
master->bus.scl_rate.i3c = val;
+ if (of_property_read_bool(i3cbus_np, "jedec,jesd403"))
+ master->bus.jesd403 = true;
+
return 0;
}
@@ -2550,6 +2766,8 @@ int i3c_master_register(struct i3c_master_controller *master,
if (ret)
goto err_del_dev;
+ i3c_bus_notify(i3cbus, I3C_NOTIFY_BUS_ADD);
+
/*
* We're done initializing the bus and the controller, we can now
* register I3C devices discovered during the initial DAA.
@@ -2584,6 +2802,8 @@ EXPORT_SYMBOL_GPL(i3c_master_register);
*/
int i3c_master_unregister(struct i3c_master_controller *master)
{
+ i3c_bus_notify(&master->bus, I3C_NOTIFY_BUS_REMOVE);
+
i3c_master_i2c_adapter_cleanup(master);
i3c_master_unregister_i3c_devs(master);
i3c_master_bus_cleanup(master);
@@ -2593,6 +2813,216 @@ int i3c_master_unregister(struct i3c_master_controller *master)
}
EXPORT_SYMBOL_GPL(i3c_master_unregister);
+static int i3c_target_bus_init(struct i3c_master_controller *master)
+{
+ return master->target_ops->bus_init(master);
+}
+
+static void i3c_target_bus_cleanup(struct i3c_master_controller *master)
+{
+ if (master->target_ops->bus_cleanup)
+ master->target_ops->bus_cleanup(master);
+}
+
+static void i3c_targetdev_release(struct device *dev)
+{
+ struct i3c_master_controller *master = container_of(dev, struct i3c_master_controller, dev);
+ struct i3c_bus *bus = &master->bus;
+
+ mutex_lock(&i3c_core_lock);
+ idr_remove(&i3c_bus_idr, bus->id);
+ mutex_unlock(&i3c_core_lock);
+
+ of_node_put(dev->of_node);
+}
+
+static void i3c_target_device_release(struct device *dev)
+{
+ struct i3c_device *i3cdev = dev_to_i3cdev(dev);
+ struct i3c_dev_desc *desc = i3cdev->desc;
+
+ kfree(i3cdev);
+ kfree(desc);
+}
+
+static void
+i3c_target_register_new_i3c_dev(struct i3c_master_controller *master, struct i3c_device_info info)
+{
+ struct i3c_dev_desc *desc;
+ int ret;
+
+ desc = kzalloc(sizeof(*desc), GFP_KERNEL);
+ if (!desc)
+ return;
+
+ desc->dev = kzalloc(sizeof(*desc->dev), GFP_KERNEL);
+ if (!desc->dev) {
+ kfree(desc);
+ return;
+ }
+
+ desc->dev->bus = &master->bus;
+ desc->dev->desc = desc;
+ desc->dev->dev.parent = &master->dev;
+ desc->dev->dev.type = &i3c_target_device_type;
+ desc->dev->dev.bus = &i3c_bus_type;
+ desc->dev->dev.release = i3c_target_device_release;
+ desc->info = info;
+ desc->common.master = master;
+ dev_set_name(&desc->dev->dev, "%d-target", master->bus.id);
+
+ ret = device_register(&desc->dev->dev);
+ if (ret)
+ dev_err(&master->dev, "Failed to add I3C target device (err = %d)\n", ret);
+
+ master->this = desc;
+ master->bus.cur_master = master->this;
+}
+
+static void i3c_target_unregister_i3c_dev(struct i3c_master_controller *master)
+{
+ struct i3c_dev_desc *i3cdev = master->this;
+
+ if (device_is_registered(&i3cdev->dev->dev))
+ device_unregister(&i3cdev->dev->dev);
+ else
+ put_device(&i3cdev->dev->dev);
+}
+
+static void i3c_target_read_device_info(struct device_node *np, struct i3c_device_info *info)
+{
+ u64 pid;
+ u32 dcr;
+ int ret;
+
+ ret = of_property_read_u64(np, "pid", &pid);
+ if (ret)
+ info->pid = 0;
+ else
+ info->pid = pid;
+
+ ret = of_property_read_u32(np, "dcr", &dcr);
+ if (ret)
+ info->dcr = 0;
+ else
+ info->dcr = dcr;
+}
+
+static int i3c_target_check_ops(const struct i3c_target_ops *ops)
+{
+ if (!ops || !ops->bus_init)
+ return -EINVAL;
+
+ return 0;
+}
+
+int i3c_target_register(struct i3c_master_controller *master, struct device *parent,
+ const struct i3c_target_ops *ops)
+{
+ struct i3c_bus *i3cbus = i3c_master_get_bus(master);
+ struct i3c_device_info info;
+ int ret;
+
+ ret = i3c_target_check_ops(ops);
+ if (ret)
+ return ret;
+
+ master->dev.parent = parent;
+ master->dev.of_node = of_node_get(parent->of_node);
+ master->dev.bus = &i3c_bus_type;
+ master->dev.release = i3c_targetdev_release;
+ master->target_ops = ops;
+ i3cbus->mode = I3C_BUS_MODE_PURE;
+
+ init_rwsem(&i3cbus->lock);
+ mutex_lock(&i3c_core_lock);
+ ret = idr_alloc(&i3c_bus_idr, i3cbus, 0, 0, GFP_KERNEL);
+ mutex_unlock(&i3c_core_lock);
+ if (ret < 0)
+ return ret;
+ i3cbus->id = ret;
+
+ device_initialize(&master->dev);
+ dev_set_name(&master->dev, "i3c-%d", i3cbus->id);
+
+ ret = device_add(&master->dev);
+ if (ret)
+ goto err_put_device;
+
+ i3c_target_read_device_info(master->dev.of_node, &info);
+
+ i3c_target_register_new_i3c_dev(master, info);
+
+ ret = i3c_target_bus_init(master);
+ if (ret)
+ goto err_cleanup_bus;
+
+ i3c_bus_notify(i3cbus, I3C_NOTIFY_BUS_ADD);
+
+ return 0;
+
+err_cleanup_bus:
+ i3c_target_bus_cleanup(master);
+
+err_put_device:
+ put_device(&master->dev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(i3c_target_register);
+
+int i3c_target_unregister(struct i3c_master_controller *master)
+{
+ i3c_bus_notify(&master->bus, I3C_NOTIFY_BUS_REMOVE);
+
+ i3c_target_unregister_i3c_dev(master);
+ i3c_target_bus_cleanup(master);
+ device_unregister(&master->dev);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(i3c_target_unregister);
+
+int i3c_target_read_register(struct i3c_device *dev, const struct i3c_target_read_setup *setup)
+{
+ dev->desc->target_info.read_handler = setup->handler;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(i3c_target_read_register);
+
+int i3c_register(struct i3c_master_controller *master,
+ struct device *parent,
+ const struct i3c_master_controller_ops *master_ops,
+ const struct i3c_target_ops *target_ops,
+ bool secondary)
+{
+ const char *role;
+ int ret;
+
+ ret = of_property_read_string(parent->of_node, "initial-role", &role);
+ if (ret || !strcmp("primary", role)) {
+ return i3c_master_register(master, parent, master_ops, secondary);
+ } else if (!strcmp("target", role)) {
+ master->target = true;
+ return i3c_target_register(master, parent, target_ops);
+ } else {
+ return -EOPNOTSUPP;
+ }
+}
+EXPORT_SYMBOL_GPL(i3c_register);
+
+int i3c_unregister(struct i3c_master_controller *master)
+{
+ if (master->target)
+ i3c_target_unregister(master);
+ else
+ i3c_master_unregister(master);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(i3c_unregister);
+
int i3c_dev_do_priv_xfers_locked(struct i3c_dev_desc *dev,
struct i3c_priv_xfer *xfers,
int nxfers)
@@ -2606,10 +3036,38 @@ int i3c_dev_do_priv_xfers_locked(struct i3c_dev_desc *dev,
if (!master || !xfers)
return -EINVAL;
- if (!master->ops->priv_xfers)
- return -ENOTSUPP;
+ if (!master->target) {
+ if (!master->ops->priv_xfers)
+ return -EOPNOTSUPP;
+
+ return master->ops->priv_xfers(dev, xfers, nxfers);
+ }
- return master->ops->priv_xfers(dev, xfers, nxfers);
+ if (!master->target_ops->priv_xfers)
+ return -EOPNOTSUPP;
+
+ return master->target_ops->priv_xfers(dev, xfers, nxfers);
+}
+
+int i3c_dev_generate_ibi_locked(struct i3c_dev_desc *dev, const u8 *data, int len)
+
+{
+ struct i3c_master_controller *master;
+
+ if (!dev)
+ return -ENOENT;
+
+ master = i3c_dev_get_master(dev);
+ if (!master)
+ return -EINVAL;
+
+ if (!master->target)
+ return -EINVAL;
+
+ if (!master->target_ops->generate_ibi)
+ return -EOPNOTSUPP;
+
+ return master->target_ops->generate_ibi(dev, data, len);
}
int i3c_dev_disable_ibi_locked(struct i3c_dev_desc *dev)
@@ -2697,6 +3155,45 @@ void i3c_dev_free_ibi_locked(struct i3c_dev_desc *dev)
dev->ibi = NULL;
}
+int i3c_dev_send_ccc_cmd_locked(struct i3c_dev_desc *dev, u8 ccc_id)
+{
+ struct i3c_master_controller *master = i3c_dev_get_master(dev);
+ int ret;
+
+ switch (ccc_id) {
+ case I3C_CCC_SETAASA:
+ ret = i3c_master_setaasa_locked(master);
+ break;
+ case I3C_CCC_SETHID:
+ ret = i3c_master_sethid_locked(master);
+ break;
+ case I3C_CCC_RSTDAA(false):
+ ret = i3c_master_rstdaa_locked(master, dev->info.dyn_addr);
+ break;
+ case I3C_CCC_RSTDAA(true):
+ ret = i3c_master_rstdaa_locked(master, I3C_BROADCAST_ADDR);
+ break;
+ default:
+ dev_err(&master->dev, "Unpermitted ccc: %x\n", ccc_id);
+ return -ENOTSUPP;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(i3c_dev_send_ccc_cmd_locked);
+
+int i3c_for_each_dev(void *data, int (*fn)(struct device *, void *))
+{
+ int res;
+
+ mutex_lock(&i3c_core_lock);
+ res = bus_for_each_dev(&i3c_bus_type, NULL, data, fn);
+ mutex_unlock(&i3c_core_lock);
+
+ return res;
+}
+EXPORT_SYMBOL_GPL(i3c_for_each_dev);
+
static int __init i3c_init(void)
{
return bus_register(&i3c_bus_type);
diff --git a/drivers/i3c/master/i3c-master-cdns.c b/drivers/i3c/master/i3c-master-cdns.c
index b9cfda6ae9ae..5b37ffe5ad5b 100644
--- a/drivers/i3c/master/i3c-master-cdns.c
+++ b/drivers/i3c/master/i3c-master-cdns.c
@@ -77,8 +77,7 @@
#define PRESCL_CTRL0 0x14
#define PRESCL_CTRL0_I2C(x) ((x) << 16)
#define PRESCL_CTRL0_I3C(x) (x)
-#define PRESCL_CTRL0_I3C_MAX GENMASK(9, 0)
-#define PRESCL_CTRL0_I2C_MAX GENMASK(15, 0)
+#define PRESCL_CTRL0_MAX GENMASK(9, 0)
#define PRESCL_CTRL1 0x18
#define PRESCL_CTRL1_PP_LOW_MASK GENMASK(15, 8)
@@ -193,7 +192,7 @@
#define SLV_STATUS1_HJ_DIS BIT(18)
#define SLV_STATUS1_MR_DIS BIT(17)
#define SLV_STATUS1_PROT_ERR BIT(16)
-#define SLV_STATUS1_DA(s) (((s) & GENMASK(15, 9)) >> 9)
+#define SLV_STATUS1_DA(x) (((s) & GENMASK(15, 9)) >> 9)
#define SLV_STATUS1_HAS_DA BIT(8)
#define SLV_STATUS1_DDR_RX_FULL BIT(7)
#define SLV_STATUS1_DDR_TX_FULL BIT(6)
@@ -1235,7 +1234,7 @@ static int cdns_i3c_master_bus_init(struct i3c_master_controller *m)
return -EINVAL;
pres = DIV_ROUND_UP(sysclk_rate, (bus->scl_rate.i3c * 4)) - 1;
- if (pres > PRESCL_CTRL0_I3C_MAX)
+ if (pres > PRESCL_CTRL0_MAX)
return -ERANGE;
bus->scl_rate.i3c = sysclk_rate / ((pres + 1) * 4);
@@ -1248,7 +1247,7 @@ static int cdns_i3c_master_bus_init(struct i3c_master_controller *m)
max_i2cfreq = bus->scl_rate.i2c;
pres = (sysclk_rate / (max_i2cfreq * 5)) - 1;
- if (pres > PRESCL_CTRL0_I2C_MAX)
+ if (pres > PRESCL_CTRL0_MAX)
return -ERANGE;
bus->scl_rate.i2c = sysclk_rate / ((pres + 1) * 5);
@@ -1625,13 +1624,13 @@ static int cdns_i3c_master_probe(struct platform_device *pdev)
/* Device ID0 is reserved to describe this master. */
master->maxdevs = CONF_STATUS0_DEVS_NUM(val);
master->free_rr_slots = GENMASK(master->maxdevs, 1);
- master->caps.ibirfifodepth = CONF_STATUS0_IBIR_DEPTH(val);
- master->caps.cmdrfifodepth = CONF_STATUS0_CMDR_DEPTH(val);
val = readl(master->regs + CONF_STATUS1);
master->caps.cmdfifodepth = CONF_STATUS1_CMD_DEPTH(val);
master->caps.rxfifodepth = CONF_STATUS1_RX_DEPTH(val);
master->caps.txfifodepth = CONF_STATUS1_TX_DEPTH(val);
+ master->caps.ibirfifodepth = CONF_STATUS0_IBIR_DEPTH(val);
+ master->caps.cmdrfifodepth = CONF_STATUS0_CMDR_DEPTH(val);
spin_lock_init(&master->ibi.lock);
master->ibi.num_slots = CONF_STATUS1_IBI_HW_RES(val);
diff --git a/drivers/i3c/master/mipi-i3c-hci/dat_v1.c b/drivers/i3c/master/mipi-i3c-hci/dat_v1.c
index 47b9b4d4ed3f..97bb49ff5b53 100644
--- a/drivers/i3c/master/mipi-i3c-hci/dat_v1.c
+++ b/drivers/i3c/master/mipi-i3c-hci/dat_v1.c
@@ -64,17 +64,15 @@ static int hci_dat_v1_init(struct i3c_hci *hci)
return -EOPNOTSUPP;
}
- if (!hci->DAT_data) {
- /* use a bitmap for faster free slot search */
- hci->DAT_data = bitmap_zalloc(hci->DAT_entries, GFP_KERNEL);
- if (!hci->DAT_data)
- return -ENOMEM;
-
- /* clear them */
- for (dat_idx = 0; dat_idx < hci->DAT_entries; dat_idx++) {
- dat_w0_write(dat_idx, 0);
- dat_w1_write(dat_idx, 0);
- }
+ /* use a bitmap for faster free slot search */
+ hci->DAT_data = bitmap_zalloc(hci->DAT_entries, GFP_KERNEL);
+ if (!hci->DAT_data)
+ return -ENOMEM;
+
+ /* clear them */
+ for (dat_idx = 0; dat_idx < hci->DAT_entries; dat_idx++) {
+ dat_w0_write(dat_idx, 0);
+ dat_w1_write(dat_idx, 0);
}
return 0;
@@ -89,13 +87,7 @@ static void hci_dat_v1_cleanup(struct i3c_hci *hci)
static int hci_dat_v1_alloc_entry(struct i3c_hci *hci)
{
unsigned int dat_idx;
- int ret;
- if (!hci->DAT_data) {
- ret = hci_dat_v1_init(hci);
- if (ret)
- return ret;
- }
dat_idx = find_first_zero_bit(hci->DAT_data, hci->DAT_entries);
if (dat_idx >= hci->DAT_entries)
return -ENOENT;
@@ -111,8 +103,7 @@ static void hci_dat_v1_free_entry(struct i3c_hci *hci, unsigned int dat_idx)
{
dat_w0_write(dat_idx, 0);
dat_w1_write(dat_idx, 0);
- if (hci->DAT_data)
- __clear_bit(dat_idx, hci->DAT_data);
+ __clear_bit(dat_idx, hci->DAT_data);
}
static void hci_dat_v1_set_dynamic_addr(struct i3c_hci *hci,
diff --git a/drivers/i3c/master/mipi-i3c-hci/dma.c b/drivers/i3c/master/mipi-i3c-hci/dma.c
index b9b6be186438..af873a9be050 100644
--- a/drivers/i3c/master/mipi-i3c-hci/dma.c
+++ b/drivers/i3c/master/mipi-i3c-hci/dma.c
@@ -291,10 +291,7 @@ static int hci_dma_init(struct i3c_hci *hci)
rh->ibi_chunk_sz = dma_get_cache_alignment();
rh->ibi_chunk_sz *= IBI_CHUNK_CACHELINES;
- if (rh->ibi_chunk_sz > 256) {
- ret = -EINVAL;
- goto err_out;
- }
+ BUG_ON(rh->ibi_chunk_sz > 256);
ibi_status_ring_sz = rh->ibi_status_sz * rh->ibi_status_entries;
ibi_data_ring_sz = rh->ibi_chunk_sz * rh->ibi_chunks_total;
@@ -348,8 +345,6 @@ static void hci_dma_unmap_xfer(struct i3c_hci *hci,
for (i = 0; i < n; i++) {
xfer = xfer_list + i;
- if (!xfer->data)
- continue;
dma_unmap_single(&hci->master.dev,
xfer->data_dma, xfer->data_len,
xfer->rnw ? DMA_FROM_DEVICE : DMA_TO_DEVICE);
@@ -455,9 +450,10 @@ static bool hci_dma_dequeue_xfer(struct i3c_hci *hci,
/*
* We're deep in it if ever this condition is ever met.
* Hardware might still be writing to memory, etc.
+ * Better suspend the world than risking silent corruption.
*/
dev_crit(&hci->master.dev, "unable to abort the ring\n");
- WARN_ON(1);
+ BUG();
}
for (i = 0; i < n; i++) {
@@ -738,7 +734,7 @@ static bool hci_dma_irq_handler(struct i3c_hci *hci, unsigned int mask)
unsigned int i;
bool handled = false;
- for (i = 0; mask && i < rings->total; i++) {
+ for (i = 0; mask && i < 8; i++) {
struct hci_rh_data *rh;
u32 status;
diff --git a/drivers/i3c/master/svc-i3c-master.c b/drivers/i3c/master/svc-i3c-master.c
index 7fc82b003b96..121e4061b2a8 100644
--- a/drivers/i3c/master/svc-i3c-master.c
+++ b/drivers/i3c/master/svc-i3c-master.c
@@ -10,14 +10,62 @@
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/completion.h>
+#include <linux/debugfs.h>
+#include <linux/dma-mapping.h>
#include <linux/errno.h>
#include <linux/i3c/master.h>
+#include <linux/i3c/target.h>
#include <linux/interrupt.h>
#include <linux/iopoll.h>
#include <linux/list.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/of.h>
+#include <linux/reset.h>
+#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+
+/* Slave Mode Registers */
+#define SVC_I3C_CONFIG 0x004
+#define SVC_I3C_CONFIG_SLVEN BIT(0)
+#define SVC_I3C_CONFIG_DDROK BIT(4)
+#define SVC_I3C_STATUS 0x008
+#define SVC_I3C_STATUS_RXPEND(x) FIELD_GET(SVC_I3C_INT_RXPEND, (x))
+#define SVC_I3C_STATUS_STREQWR(x) (x & BIT(4))
+#define SVC_I3C_STATUS_DDRMATCH BIT(16)
+#define SVC_I3C_STATUS_STOP BIT(10)
+#define SVC_I3C_CTRL 0x00C
+#define SVC_I3C_CTRL_EVENT(x) FIELD_PREP(GENMASK(1, 0), (x))
+#define SVC_I3C_CTRL_EVENT_HOT_JOIN 3
+#define SVC_I3C_CTRL_PENDINT(x) FIELD_PREP(GENMASK(19, 16), (x))
+#define SVC_I3C_INTSET 0x010
+#define SVC_I3C_INT_STOP BIT(10)
+#define SVC_I3C_INT_RXPEND BIT(11)
+#define SVC_I3C_INT_TXNOTFULL BIT(12)
+#define SVC_I3C_INTCLR 0x014
+#define SVC_I3C_INTMASKED 0x018
+#define SVC_I3C_ERRWARN 0x01C
+#define SVC_I3C_DMACTRL 0x020
+#define SVC_I3C_DMACTRL_DMAFB(x) FIELD_PREP(GENMASK(1, 0), (x))
+#define SVC_I3C_DMACTRL_DMATB(x) FIELD_PREP(GENMASK(3, 2), (x))
+#define SVC_I3C_DMACTRL_DMAWIDTH(x) FIELD_PREP(GENMASK(5, 4), (x))
+#define SVC_I3C_DATACTRL 0x02C
+#define SVC_I3C_DATACTRL_FLUSHTB BIT(0)
+#define SVC_I3C_DATACTRL_FLUSHRB BIT(1)
+#define SVC_I3C_DATACTRL_TXFULL BIT(30)
+#define SVC_I3C_DATACTRL_TXCOUNT(x) FIELD_GET(GENMASK(20, 16), (x))
+#define SVC_I3C_DATACTRL_RXCOUNT(x) FIELD_GET(GENMASK(28, 24), (x))
+#define SVC_I3C_WDATAB 0x030
+#define SVC_I3C_WDATABE 0x034
+#define SVC_I3C_RDATAB 0x040
+#define SVC_I3C_MAXLIMITS 0x068
+#define SVC_I3C_MAXLIMITS_MAXWR(x) FIELD_PREP(GENMASK(27, 16), (x))
+#define SVC_I3C_MAXLIMITS_MAXRD(x) FIELD_PREP(GENMASK(11, 0), (x))
+#define SVC_I3C_PARTNO 0x06C
+#define SVC_I3C_IDEXT 0x070
+#define SVC_I3C_IDEXT_BCR(x) FIELD_PREP(GENMASK(23 16), (x))
+#define SVC_I3C_IDEXT_DCR(x) FIELD_PREP(GENMASK(15, 8), (x))
/* Master Mode Registers */
#define SVC_I3C_MCONFIG 0x000
@@ -30,18 +78,22 @@
#define SVC_I3C_MCONFIG_ODBAUD(x) FIELD_PREP(GENMASK(23, 16), (x))
#define SVC_I3C_MCONFIG_ODHPP(x) FIELD_PREP(BIT(24), (x))
#define SVC_I3C_MCONFIG_SKEW(x) FIELD_PREP(GENMASK(27, 25), (x))
+#define SVC_I3C_MCONFIG_SKEW_MASK GENMASK(27, 25)
#define SVC_I3C_MCONFIG_I2CBAUD(x) FIELD_PREP(GENMASK(31, 28), (x))
#define SVC_I3C_MCTRL 0x084
#define SVC_I3C_MCTRL_REQUEST_MASK GENMASK(2, 0)
+#define SVC_I3C_MCTRL_REQUEST(x) FIELD_GET(GENMASK(2, 0), (x))
#define SVC_I3C_MCTRL_REQUEST_NONE 0
#define SVC_I3C_MCTRL_REQUEST_START_ADDR 1
#define SVC_I3C_MCTRL_REQUEST_STOP 2
#define SVC_I3C_MCTRL_REQUEST_IBI_ACKNACK 3
#define SVC_I3C_MCTRL_REQUEST_PROC_DAA 4
+#define SVC_I3C_MCTRL_REQUEST_FORCE_EXIT 6
#define SVC_I3C_MCTRL_REQUEST_AUTO_IBI 7
#define SVC_I3C_MCTRL_TYPE_I3C 0
#define SVC_I3C_MCTRL_TYPE_I2C BIT(4)
+#define SVC_I3C_MCTRL_TYPE_I3C_DDR BIT(5)
#define SVC_I3C_MCTRL_IBIRESP_AUTO 0
#define SVC_I3C_MCTRL_IBIRESP_ACK_WITHOUT_BYTE 0
#define SVC_I3C_MCTRL_IBIRESP_ACK_WITH_BYTE BIT(7)
@@ -57,6 +109,8 @@
#define SVC_I3C_MSTATUS_STATE(x) FIELD_GET(GENMASK(2, 0), (x))
#define SVC_I3C_MSTATUS_STATE_DAA(x) (SVC_I3C_MSTATUS_STATE(x) == 5)
#define SVC_I3C_MSTATUS_STATE_IDLE(x) (SVC_I3C_MSTATUS_STATE(x) == 0)
+#define SVC_I3C_MSTATUS_STATE_SLVREQ(x) (SVC_I3C_MSTATUS_STATE(x) == 1)
+#define SVC_I3C_MSTATUS_STATE_IBIACK(x) (SVC_I3C_MSTATUS_STATE(x) == 6)
#define SVC_I3C_MSTATUS_BETWEEN(x) FIELD_GET(BIT(4), (x))
#define SVC_I3C_MSTATUS_NACKED(x) FIELD_GET(BIT(5), (x))
#define SVC_I3C_MSTATUS_IBITYPE(x) FIELD_GET(GENMASK(7, 6), (x))
@@ -90,7 +144,13 @@
#define SVC_I3C_MINTCLR 0x094
#define SVC_I3C_MINTMASKED 0x098
#define SVC_I3C_MERRWARN 0x09C
+#define SVC_I3C_MERRWARN_NACK(x) FIELD_GET(BIT(2), (x))
+#define SVC_I3C_MERRWARN_TIMEOUT BIT(20)
+#define SVC_I3C_MERRWARN_HCRC(x) FIELD_GET(BIT(10), (x))
#define SVC_I3C_MDMACTRL 0x0A0
+#define SVC_I3C_MDMACTRL_DMAFB(x) FIELD_PREP(GENMASK(1, 0), (x))
+#define SVC_I3C_MDMACTRL_DMATB(x) FIELD_PREP(GENMASK(3, 2), (x))
+#define SVC_I3C_MDMACTRL_DMAWIDTH(x) FIELD_PREP(GENMASK(5, 4), (x))
#define SVC_I3C_MDATACTRL 0x0AC
#define SVC_I3C_MDATACTRL_FLUSHTB BIT(0)
#define SVC_I3C_MDATACTRL_FLUSHRB BIT(1)
@@ -98,6 +158,7 @@
#define SVC_I3C_MDATACTRL_TXTRIG_FIFO_NOT_FULL GENMASK(5, 4)
#define SVC_I3C_MDATACTRL_RXTRIG_FIFO_NOT_EMPTY 0
#define SVC_I3C_MDATACTRL_RXCOUNT(x) FIELD_GET(GENMASK(28, 24), (x))
+#define SVC_I3C_MDATACTRL_TXCOUNT(x) FIELD_GET(GENMASK(20, 16), (x))
#define SVC_I3C_MDATACTRL_TXFULL BIT(30)
#define SVC_I3C_MDATACTRL_RXEMPTY BIT(31)
@@ -118,10 +179,44 @@
#define SVC_MDYNADDR_VALID BIT(0)
#define SVC_MDYNADDR_ADDR(x) FIELD_PREP(GENMASK(7, 1), (x))
+#define SVC_I3C_PARTNO 0x06C
+#define SVC_I3C_VENDORID 0x074
+#define SVC_I3C_VENDORID_VID(x) FIELD_GET(GENMASK(14, 0), (x))
+
#define SVC_I3C_MAX_DEVS 32
+#define SVC_I3C_PM_TIMEOUT_MS 1000
+#define HDR_COMMAND 0x20
/* This parameter depends on the implementation and may be tuned */
#define SVC_I3C_FIFO_SIZE 16
+#define SVC_I3C_MAX_IBI_PAYLOAD_SIZE 8
+#define SVC_I3C_MAX_RDTERM 255
+#define SVC_I3C_MAX_PPBAUD 15
+#define SVC_I3C_MAX_PPLOW 15
+#define SVC_I3C_MAX_ODBAUD 255
+#define SVC_I3C_MAX_I2CBAUD 15
+#define I3C_SCL_PP_PERIOD_NS_MIN 40
+#define I3C_SCL_OD_LOW_PERIOD_NS_MIN 200
+
+/* DMA definitions */
+#define MAX_DMA_COUNT 1024
+#define DMA_CH_TX 0
+#define DMA_CH_RX 1
+#define NPCM_GDMA_CTL(n) (n * 0x20 + 0x00)
+#define NPCM_GDMA_CTL_GDMAMS(x) FIELD_PREP(GENMASK(3, 2), (x))
+#define NPCM_GDMA_CTL_TWS(x) FIELD_PREP(GENMASK(13, 12), (x))
+#define NPCM_GDMA_CTL_GDMAEN BIT(0)
+#define NPCM_GDMA_CTL_DAFIX BIT(6)
+#define NPCM_GDMA_CTL_SAFIX BIT(7)
+#define NPCM_GDMA_CTL_SIEN BIT(8)
+#define NPCM_GDMA_CTL_DM BIT(15)
+#define NPCM_GDMA_CTL_TC BIT(18)
+#define NPCM_GDMA_SRCB(n) (n * 0x20 + 0x04)
+#define NPCM_GDMA_DSTB(n) (n * 0x20 + 0x08)
+#define NPCM_GDMA_TCNT(n) (n * 0x20 + 0x0C)
+#define NPCM_GDMA_CSRC(n) (n * 0x20 + 0x10)
+#define NPCM_GDMA_CDST(n) (n * 0x20 + 0x14)
+#define NPCM_GDMA_CTCNT(n) (n * 0x20 + 0x18)
struct svc_i3c_cmd {
u8 addr;
@@ -131,6 +226,7 @@ struct svc_i3c_cmd {
unsigned int len;
unsigned int read_len;
bool continued;
+ bool use_dma;
};
struct svc_i3c_xfer {
@@ -142,16 +238,28 @@ struct svc_i3c_xfer {
struct svc_i3c_cmd cmds[];
};
+struct svc_i3c_regs_save {
+ u32 mconfig;
+ u32 mdynaddr;
+};
+
+struct npcm_dma_xfer_desc {
+ const u8 *out;
+ u8 *in;
+ u32 len;
+ bool rnw;
+ bool end;
+};
/**
* struct svc_i3c_master - Silvaco I3C Master structure
* @base: I3C master controller
* @dev: Corresponding device
* @regs: Memory mapping
+ * @saved_regs: Volatile values for PM operations
* @free_slots: Bit array of available slots
* @addrs: Array containing the dynamic addresses of each attached device
* @descs: Array of descriptors, one per attached device
* @hj_work: Hot-join work
- * @ibi_work: IBI work
* @irq: Main interrupt
* @pclk: System clock
* @fclk: Fast clock (bus)
@@ -165,26 +273,31 @@ struct svc_i3c_xfer {
* @ibi.slots: Available IBI slots
* @ibi.tbq_slot: To be queued IBI slot
* @ibi.lock: IBI lock
- * @lock: Transfer lock, protect between IBI work thread and callbacks from master
+ * @lock: Transfer lock, prevent concurrent daa/priv_xfer/ccc
+ * @req_lock: protect between IBI isr and bus operation request
*/
struct svc_i3c_master {
struct i3c_master_controller base;
struct device *dev;
void __iomem *regs;
+ struct svc_i3c_regs_save saved_regs;
u32 free_slots;
u8 addrs[SVC_I3C_MAX_DEVS];
struct i3c_dev_desc *descs[SVC_I3C_MAX_DEVS];
struct work_struct hj_work;
- struct work_struct ibi_work;
int irq;
struct clk *pclk;
struct clk *fclk;
struct clk *sclk;
+ struct {
+ u32 i3c_pp_hi;
+ u32 i3c_pp_lo;
+ u32 i3c_od_hi;
+ u32 i3c_od_lo;
+ } scl_timing;
struct {
struct list_head list;
struct svc_i3c_xfer *cur;
- /* Prevent races between transfers */
- spinlock_t lock;
} xferqueue;
struct {
unsigned int num_slots;
@@ -193,7 +306,38 @@ struct svc_i3c_master {
/* Prevent races within IBI handlers */
spinlock_t lock;
} ibi;
+ spinlock_t req_lock;
struct mutex lock;
+ struct dentry *debugfs;
+
+ struct {
+ struct svc_i3c_xfer *cur;
+ struct svc_i3c_xfer *pending_rd;
+ spinlock_t lock;
+ } slave;
+
+ /* For DMA */
+ void __iomem *dma_regs;
+ void __iomem *dma_mux_regs;
+ bool use_dma;
+ struct completion xfer_comp;
+ char *dma_tx_buf;
+ char *dma_rx_buf;
+ dma_addr_t dma_tx_addr;
+ dma_addr_t dma_rx_addr;
+ struct npcm_dma_xfer_desc dma_xfer;
+
+ bool en_hj;
+ bool hdr_ddr;
+ bool hdr_mode;
+ bool dma_started;
+ bool probe_done;
+
+ /* Statistic report */
+ u8 err_code;
+ u64 err_cnt;
+ u64 rd_ibiwon_cnt;
+ u64 wr_ibiwon_cnt;
};
/**
@@ -208,6 +352,15 @@ struct svc_i3c_i2c_dev_data {
struct i3c_generic_ibi_pool *ibi_pool;
};
+static int svc_i3c_master_wait_for_complete(struct svc_i3c_master *master);
+static void svc_i3c_master_stop_dma(struct svc_i3c_master *master);
+
+static void svc_i3c_master_err_stats(struct svc_i3c_master *master, u8 code)
+{
+ master->err_cnt++;
+ master->err_code = code;
+}
+
static bool svc_i3c_master_error(struct svc_i3c_master *master)
{
u32 mstatus, merrwarn;
@@ -216,6 +369,14 @@ static bool svc_i3c_master_error(struct svc_i3c_master *master)
if (SVC_I3C_MSTATUS_ERRWARN(mstatus)) {
merrwarn = readl(master->regs + SVC_I3C_MERRWARN);
writel(merrwarn, master->regs + SVC_I3C_MERRWARN);
+
+ /* Ignore timeout error */
+ if (merrwarn & SVC_I3C_MERRWARN_TIMEOUT) {
+ dev_dbg(master->dev, "Warning condition: MSTATUS 0x%08x, MERRWARN 0x%08x\n",
+ mstatus, merrwarn);
+ return false;
+ }
+
dev_err(master->dev,
"Error condition: MSTATUS 0x%08x, MERRWARN 0x%08x\n",
mstatus, merrwarn);
@@ -238,6 +399,45 @@ static void svc_i3c_master_disable_interrupts(struct svc_i3c_master *master)
writel(mask, master->regs + SVC_I3C_MINTCLR);
}
+static void svc_i3c_master_clear_merrwarn(struct svc_i3c_master *master)
+{
+ /* Clear pending warnings */
+ writel(readl(master->regs + SVC_I3C_MERRWARN),
+ master->regs + SVC_I3C_MERRWARN);
+}
+
+static void svc_i3c_master_flush_fifo(struct svc_i3c_master *master)
+{
+ /* Flush FIFOs */
+ writel(SVC_I3C_MDATACTRL_FLUSHTB | SVC_I3C_MDATACTRL_FLUSHRB,
+ master->regs + SVC_I3C_MDATACTRL);
+}
+
+static void svc_i3c_master_flush_rx_fifo(struct svc_i3c_master *master)
+{
+ writel(SVC_I3C_MDATACTRL_FLUSHRB, master->regs + SVC_I3C_MDATACTRL);
+}
+
+static void svc_i3c_master_reset_fifo_trigger(struct svc_i3c_master *master)
+{
+ u32 reg;
+
+ /* Set RX and TX tigger levels, flush FIFOs */
+ reg = SVC_I3C_MDATACTRL_FLUSHTB |
+ SVC_I3C_MDATACTRL_FLUSHRB |
+ SVC_I3C_MDATACTRL_UNLOCK_TRIG |
+ SVC_I3C_MDATACTRL_TXTRIG_FIFO_NOT_FULL |
+ SVC_I3C_MDATACTRL_RXTRIG_FIFO_NOT_EMPTY;
+ writel(reg, master->regs + SVC_I3C_MDATACTRL);
+}
+
+static void svc_i3c_master_reset(struct svc_i3c_master *master)
+{
+ svc_i3c_master_clear_merrwarn(master);
+ svc_i3c_master_reset_fifo_trigger(master);
+ svc_i3c_master_disable_interrupts(master);
+}
+
static inline struct svc_i3c_master *
to_svc_i3c_master(struct i3c_master_controller *master)
{
@@ -249,6 +449,7 @@ static void svc_i3c_master_hj_work(struct work_struct *work)
struct svc_i3c_master *master;
master = container_of(work, struct svc_i3c_master, hj_work);
+
i3c_master_do_daa(&master->base);
}
@@ -268,9 +469,64 @@ svc_i3c_master_dev_from_addr(struct svc_i3c_master *master,
return master->descs[i];
}
+static void svc_i3c_master_ack_ibi(struct svc_i3c_master *master,
+ bool mandatory_byte)
+{
+ unsigned int ibi_ack_nack;
+ u32 reg;
+
+ ibi_ack_nack = SVC_I3C_MCTRL_REQUEST_IBI_ACKNACK;
+ if (mandatory_byte)
+ ibi_ack_nack |= SVC_I3C_MCTRL_IBIRESP_ACK_WITH_BYTE |
+ SVC_I3C_MCTRL_RDTERM(SVC_I3C_MAX_IBI_PAYLOAD_SIZE);
+ else
+ ibi_ack_nack |= SVC_I3C_MCTRL_IBIRESP_ACK_WITHOUT_BYTE;
+
+ writel(ibi_ack_nack, master->regs + SVC_I3C_MCTRL);
+ readl_poll_timeout(master->regs + SVC_I3C_MSTATUS, reg,
+ SVC_I3C_MSTATUS_MCTRLDONE(reg), 0, 1000);
+}
+
+static void svc_i3c_master_nack_ibi(struct svc_i3c_master *master)
+{
+ u32 reg;
+
+ writel(SVC_I3C_MCTRL_REQUEST_IBI_ACKNACK |
+ SVC_I3C_MCTRL_IBIRESP_NACK,
+ master->regs + SVC_I3C_MCTRL);
+ readl_poll_timeout(master->regs + SVC_I3C_MSTATUS, reg,
+ SVC_I3C_MSTATUS_MCTRLDONE(reg), 0, 1000);
+}
+
static void svc_i3c_master_emit_stop(struct svc_i3c_master *master)
{
- writel(SVC_I3C_MCTRL_REQUEST_STOP, master->regs + SVC_I3C_MCTRL);
+ u32 reg = readl(master->regs + SVC_I3C_MSTATUS);
+
+ /* Do not emit stop in the IDLE or SLVREQ state */
+ if (SVC_I3C_MSTATUS_STATE_IDLE(reg) || SVC_I3C_MSTATUS_STATE_SLVREQ(reg))
+ return;
+
+ /*
+ * The spurious IBI event may change controller state to IBIACK, switch state
+ * to NORMACT before emitSTOP request.
+ */
+ if (SVC_I3C_MSTATUS_STATE_IBIACK(reg)) {
+ svc_i3c_master_nack_ibi(master);
+ writel(SVC_I3C_MINT_IBIWON, master->regs + SVC_I3C_MSTATUS);
+ }
+
+ if (master->hdr_mode) {
+ writel(SVC_I3C_MCTRL_REQUEST_FORCE_EXIT, master->regs + SVC_I3C_MCTRL);
+ master->hdr_mode = false;
+ } else {
+ writel(SVC_I3C_MCTRL_REQUEST_STOP, master->regs + SVC_I3C_MCTRL);
+ /*
+ * Wait for STOP condition to complete, in case the subsequent
+ * EmitStartAddr request is issued too early.
+ */
+ readl_poll_timeout(master->regs + SVC_I3C_MSTATUS, reg,
+ SVC_I3C_MSTATUS_MCTRLDONE(reg), 0, 1000);
+ }
/*
* This delay is necessary after the emission of a stop, otherwise eg.
@@ -281,126 +537,134 @@ static void svc_i3c_master_emit_stop(struct svc_i3c_master *master)
udelay(1);
}
-static void svc_i3c_master_clear_merrwarn(struct svc_i3c_master *master)
-{
- writel(readl(master->regs + SVC_I3C_MERRWARN),
- master->regs + SVC_I3C_MERRWARN);
-}
-
static int svc_i3c_master_handle_ibi(struct svc_i3c_master *master,
struct i3c_dev_desc *dev)
{
struct svc_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
struct i3c_ibi_slot *slot;
unsigned int count;
- u32 mdatactrl;
- int ret, val;
+ u32 mdatactrl, val;
+ int ret;
u8 *buf;
+ if (!data) {
+ dev_err_ratelimited(master->dev, "No data for addr 0x%x\n",
+ dev->info.dyn_addr);
+ goto no_ibi_pool;
+ }
+ if (!data->ibi_pool) {
+ dev_err_ratelimited(master->dev, "No ibi pool for addr 0x%x\n",
+ master->addrs[data->index]);
+ goto no_ibi_pool;
+ }
slot = i3c_generic_ibi_get_free_slot(data->ibi_pool);
- if (!slot)
- return -ENOSPC;
+ if (!slot) {
+ dev_err_ratelimited(master->dev, "No free ibi slot\n");
+ goto no_ibi_pool;
+ }
slot->len = 0;
buf = slot->data;
+ /*
+ * Sometimes I3C HW returns to IDLE state after IBIRCV completed,
+ * continue when state becomes IDLE.
+ */
ret = readl_relaxed_poll_timeout(master->regs + SVC_I3C_MSTATUS, val,
- SVC_I3C_MSTATUS_COMPLETE(val), 0, 1000);
+ SVC_I3C_MSTATUS_COMPLETE(val) |
+ SVC_I3C_MSTATUS_STATE_IDLE(val),
+ 0, 1000);
if (ret) {
dev_err(master->dev, "Timeout when polling for COMPLETE\n");
- return ret;
+ /* The event is wrong, do not deliver it to upper layer. */
+ if (SVC_I3C_MSTATUS_RXPEND(val))
+ svc_i3c_master_flush_rx_fifo(master);
+ i3c_generic_ibi_recycle_slot(data->ibi_pool, slot);
+ slot = NULL;
+ svc_i3c_master_err_stats(master, ETIMEDOUT);
+ goto handle_done;
}
while (SVC_I3C_MSTATUS_RXPEND(readl(master->regs + SVC_I3C_MSTATUS)) &&
- slot->len < SVC_I3C_FIFO_SIZE) {
+ slot->len < SVC_I3C_MAX_IBI_PAYLOAD_SIZE) {
mdatactrl = readl(master->regs + SVC_I3C_MDATACTRL);
count = SVC_I3C_MDATACTRL_RXCOUNT(mdatactrl);
- readsl(master->regs + SVC_I3C_MRDATAB, buf, count);
+ readsb(master->regs + SVC_I3C_MRDATAB, buf, count);
slot->len += count;
buf += count;
}
+handle_done:
master->ibi.tbq_slot = slot;
- return 0;
-}
-
-static void svc_i3c_master_ack_ibi(struct svc_i3c_master *master,
- bool mandatory_byte)
-{
- unsigned int ibi_ack_nack;
-
- ibi_ack_nack = SVC_I3C_MCTRL_REQUEST_IBI_ACKNACK;
- if (mandatory_byte)
- ibi_ack_nack |= SVC_I3C_MCTRL_IBIRESP_ACK_WITH_BYTE;
- else
- ibi_ack_nack |= SVC_I3C_MCTRL_IBIRESP_ACK_WITHOUT_BYTE;
-
- writel(ibi_ack_nack, master->regs + SVC_I3C_MCTRL);
-}
+ return ret;
-static void svc_i3c_master_nack_ibi(struct svc_i3c_master *master)
-{
- writel(SVC_I3C_MCTRL_REQUEST_IBI_ACKNACK |
- SVC_I3C_MCTRL_IBIRESP_NACK,
- master->regs + SVC_I3C_MCTRL);
+no_ibi_pool:
+ /* No ibi pool, drop the payload if received */
+ readl_relaxed_poll_timeout(master->regs + SVC_I3C_MSTATUS, val,
+ SVC_I3C_MSTATUS_COMPLETE(val) |
+ SVC_I3C_MSTATUS_STATE_IDLE(val),
+ 0, 1000);
+ svc_i3c_master_flush_rx_fifo(master);
+ return -ENOSPC;
}
-static void svc_i3c_master_ibi_work(struct work_struct *work)
+static int svc_i3c_master_handle_ibiwon(struct svc_i3c_master *master, bool autoibi)
{
- struct svc_i3c_master *master = container_of(work, struct svc_i3c_master, ibi_work);
struct svc_i3c_i2c_dev_data *data;
unsigned int ibitype, ibiaddr;
struct i3c_dev_desc *dev;
- u32 status, val;
- int ret;
-
- mutex_lock(&master->lock);
- /*
- * IBIWON may be set before SVC_I3C_MCTRL_REQUEST_AUTO_IBI, causing
- * readl_relaxed_poll_timeout() to return immediately. Consequently,
- * ibitype will be 0 since it was last updated only after the 8th SCL
- * cycle, leading to missed client IBI handlers.
- *
- * A typical scenario is when IBIWON occurs and bus arbitration is lost
- * at svc_i3c_master_priv_xfers().
- *
- * Clear SVC_I3C_MINT_IBIWON before sending SVC_I3C_MCTRL_REQUEST_AUTO_IBI.
- */
- writel(SVC_I3C_MINT_IBIWON, master->regs + SVC_I3C_MSTATUS);
-
- /* Acknowledge the incoming interrupt with the AUTOIBI mechanism */
- writel(SVC_I3C_MCTRL_REQUEST_AUTO_IBI |
- SVC_I3C_MCTRL_IBIRESP_AUTO,
- master->regs + SVC_I3C_MCTRL);
-
- /* Wait for IBIWON, should take approximately 100us */
- ret = readl_relaxed_poll_timeout(master->regs + SVC_I3C_MSTATUS, val,
- SVC_I3C_MSTATUS_IBIWON(val), 0, 1000);
- if (ret) {
- dev_err(master->dev, "Timeout when polling for IBIWON\n");
- svc_i3c_master_emit_stop(master);
- goto reenable_ibis;
- }
+ u32 status;
+ int ret = 0;
status = readl(master->regs + SVC_I3C_MSTATUS);
ibitype = SVC_I3C_MSTATUS_IBITYPE(status);
ibiaddr = SVC_I3C_MSTATUS_IBIADDR(status);
+ dev_dbg(master->dev, "ibitype=%d ibiaddr=%d\n", ibitype, ibiaddr);
+ dev_dbg(master->dev, "ibiwon: mctrl=0x%x mstatus=0x%x\n",
+ readl(master->regs + SVC_I3C_MCTRL), status);
/* Handle the critical responses to IBI's */
switch (ibitype) {
case SVC_I3C_MSTATUS_IBITYPE_IBI:
dev = svc_i3c_master_dev_from_addr(master, ibiaddr);
- if (!dev)
- svc_i3c_master_nack_ibi(master);
- else
- svc_i3c_master_handle_ibi(master, dev);
+ /* Bypass the invalid ibi with address 0 */
+ if (!dev || ibiaddr == 0) {
+ if (!autoibi) {
+ svc_i3c_master_nack_ibi(master);
+ break;
+ }
+ /*
+ * Wait for complete to make sure the subsequent emitSTOP
+ * request will be performed in the correct state(NORMACT).
+ */
+ readl_relaxed_poll_timeout(master->regs + SVC_I3C_MSTATUS, status,
+ SVC_I3C_MSTATUS_COMPLETE(status),
+ 0, 1000);
+ /* Flush the garbage data */
+ if (SVC_I3C_MSTATUS_RXPEND(status))
+ svc_i3c_master_flush_rx_fifo(master);
+ break;
+ }
+ if (!autoibi) {
+ if (dev->info.bcr & I3C_BCR_IBI_PAYLOAD)
+ svc_i3c_master_ack_ibi(master, true);
+ else
+ svc_i3c_master_ack_ibi(master, false);
+ }
+ svc_i3c_master_handle_ibi(master, dev);
break;
case SVC_I3C_MSTATUS_IBITYPE_HOT_JOIN:
svc_i3c_master_ack_ibi(master, false);
break;
case SVC_I3C_MSTATUS_IBITYPE_MASTER_REQUEST:
svc_i3c_master_nack_ibi(master);
+ status = readl(master->regs + SVC_I3C_MSTATUS);
+ /* Invalid event may be reported as MR request
+ * and sometimes produce dummy bytes. Flush the garbage data.
+ */
+ if (SVC_I3C_MSTATUS_RXPEND(status))
+ svc_i3c_master_flush_rx_fifo(master);
break;
default:
break;
@@ -419,48 +683,155 @@ static void svc_i3c_master_ibi_work(struct work_struct *work)
master->ibi.tbq_slot = NULL;
}
- svc_i3c_master_emit_stop(master);
-
- goto reenable_ibis;
+ dev_err(master->dev, "svc_i3c_master_error in ibiwon\n");
+ /*
+ * No need to emit stop here because the caller should do it
+ * if return error
+ */
+ ret = -EIO;
+ svc_i3c_master_err_stats(master, EIO);
+ goto clear_ibiwon;
}
/* Handle the non critical tasks */
switch (ibitype) {
case SVC_I3C_MSTATUS_IBITYPE_IBI:
- if (dev) {
+ /*
+ * Sometimes I3C HW returns to IDLE state after IBIRCV completed,
+ * do not emit STOP in the idle state.
+ */
+ svc_i3c_master_emit_stop(master);
+ if (dev && master->ibi.tbq_slot) {
i3c_master_queue_ibi(dev, master->ibi.tbq_slot);
master->ibi.tbq_slot = NULL;
}
- svc_i3c_master_emit_stop(master);
break;
case SVC_I3C_MSTATUS_IBITYPE_HOT_JOIN:
+ /* Emit stop to avoid the INVREQ error after DAA process */
+ svc_i3c_master_emit_stop(master);
queue_work(master->base.wq, &master->hj_work);
break;
case SVC_I3C_MSTATUS_IBITYPE_MASTER_REQUEST:
+ ret = -EOPNOTSUPP;
+ svc_i3c_master_err_stats(master, EOPNOTSUPP);
default:
break;
}
-reenable_ibis:
- svc_i3c_master_enable_interrupts(master, SVC_I3C_MINT_SLVSTART);
- mutex_unlock(&master->lock);
+clear_ibiwon:
+ /* clear IBIWON status */
+ writel(SVC_I3C_MINT_IBIWON, master->regs + SVC_I3C_MSTATUS);
+ return ret;
+}
+
+static void svc_i3c_master_ibi_isr(struct svc_i3c_master *master)
+{
+ u32 val, mstatus;
+ int ret;
+
+ spin_lock(&master->req_lock);
+
+ /* Check slave ibi handled not yet */
+ mstatus = readl(master->regs + SVC_I3C_MSTATUS);
+ if (!SVC_I3C_MSTATUS_STATE_SLVREQ(mstatus))
+ goto ibi_out;
+
+ /*
+ * IBIWON may be set before SVC_I3C_MCTRL_REQUEST_AUTO_IBI, causing
+ * readl_relaxed_poll_timeout() to return immediately. Consequently,
+ * ibitype will be 0 since it was last updated only after the 8th SCL
+ * cycle, leading to missed client IBI handlers.
+ *
+ * A typical scenario is when IBIWON occurs and bus arbitration is lost
+ * at svc_i3c_master_priv_xfers().
+ *
+ * Clear SVC_I3C_MINT_IBIWON before sending SVC_I3C_MCTRL_REQUEST_AUTO_IBI.
+ */
+ writel(SVC_I3C_MINT_IBIWON, master->regs + SVC_I3C_MSTATUS);
+
+ /* Acknowledge the incoming interrupt with the AUTOIBI mechanism */
+ writel(SVC_I3C_MCTRL_REQUEST_AUTO_IBI |
+ SVC_I3C_MCTRL_IBIRESP_AUTO |
+ SVC_I3C_MCTRL_RDTERM(SVC_I3C_MAX_IBI_PAYLOAD_SIZE),
+ master->regs + SVC_I3C_MCTRL);
+
+ /* Wait for IBIWON, should take approximately 100us */
+ ret = readl_relaxed_poll_timeout_atomic(master->regs + SVC_I3C_MSTATUS, val,
+ SVC_I3C_MSTATUS_IBIWON(val), 0, 1000);
+ if (ret) {
+ /* Cancle AUTOIBI if not started */
+ val = readl(master->regs + SVC_I3C_MCTRL);
+ if (SVC_I3C_MCTRL_REQUEST(val) == SVC_I3C_MCTRL_REQUEST_AUTO_IBI)
+ writel(0, master->regs + SVC_I3C_MCTRL);
+
+ dev_err(master->dev, "Timeout when polling for IBIWON\n");
+ svc_i3c_master_clear_merrwarn(master);
+ svc_i3c_master_emit_stop(master);
+ svc_i3c_master_err_stats(master, ETIMEDOUT);
+ goto ibi_out;
+ }
+
+ if (svc_i3c_master_handle_ibiwon(master, true))
+ svc_i3c_master_emit_stop(master);
+ibi_out:
+ spin_unlock(&master->req_lock);
}
static irqreturn_t svc_i3c_master_irq_handler(int irq, void *dev_id)
{
struct svc_i3c_master *master = (struct svc_i3c_master *)dev_id;
- u32 active = readl(master->regs + SVC_I3C_MSTATUS);
+ u32 active = readl(master->regs + SVC_I3C_MINTMASKED), mstatus;
+
+ if (SVC_I3C_MSTATUS_COMPLETE(active)) {
+ /* Clear COMPLETE status before emit STOP */
+ writel(SVC_I3C_MINT_COMPLETE, master->regs + SVC_I3C_MSTATUS);
+ /* Disable COMPLETE interrupt */
+ writel(SVC_I3C_MINT_COMPLETE, master->regs + SVC_I3C_MINTCLR);
+
+ if (master->dma_xfer.end) {
+ /* Stop DMA to prevent receiving the data of other transaction */
+ svc_i3c_master_stop_dma(master);
+ svc_i3c_master_emit_stop(master);
+ }
- if (!SVC_I3C_MSTATUS_SLVSTART(active))
- return IRQ_NONE;
+ complete(&master->xfer_comp);
- /* Clear the interrupt status */
- writel(SVC_I3C_MINT_SLVSTART, master->regs + SVC_I3C_MSTATUS);
+ return IRQ_HANDLED;
+ }
- svc_i3c_master_disable_interrupts(master);
+ if (SVC_I3C_MSTATUS_SLVSTART(active)) {
+ /* Clear the interrupt status */
+ writel(SVC_I3C_MINT_SLVSTART, master->regs + SVC_I3C_MSTATUS);
+
+ /* Read I3C state */
+ mstatus = readl(master->regs + SVC_I3C_MSTATUS);
+
+ if (SVC_I3C_MSTATUS_STATE_SLVREQ(mstatus)) {
+ svc_i3c_master_ibi_isr(master);
+ } else {
+ /*
+ * Workaround:
+ * SlaveStart event under bad signals condition. SLVSTART bit in
+ * MSTATUS may set even slave device doesn't holding I3C_SDA low,
+ * but actual SlaveStart event may happened concurently in this
+ * bad signals condition handler. Give a chance to check current
+ * work state and intmask to avoid actual SlaveStart cannot be
+ * trigger after we clear SlaveStart interrupt status.
+ */
+
+ /* Check if state change after we clear interrupt status */
+ active = readl(master->regs + SVC_I3C_MINTMASKED);
+ mstatus = readl(master->regs + SVC_I3C_MSTATUS);
- /* Handle the interrupt in a non atomic context */
- queue_work(master->base.wq, &master->ibi_work);
+ if (SVC_I3C_MSTATUS_STATE_SLVREQ(mstatus)) {
+ if (!SVC_I3C_MSTATUS_SLVSTART(active)) {
+ svc_i3c_master_ibi_isr(master);
+ } else {
+ /* handle interrupt in next time */
+ }
+ }
+ }
+ }
return IRQ_HANDLED;
}
@@ -471,96 +842,161 @@ static int svc_i3c_master_bus_init(struct i3c_master_controller *m)
struct i3c_bus *bus = i3c_master_get_bus(m);
struct i3c_device_info info = {};
unsigned long fclk_rate, fclk_period_ns;
- unsigned int high_period_ns, od_low_period_ns;
- u32 ppbaud, pplow, odhpp, odbaud, i2cbaud, reg;
+ unsigned long i3c_scl_rate, i2c_scl_rate;
+ unsigned int pp_high_period_ns, od_low_period_ns, i2c_period_ns;
+ unsigned int scl_period_ns;
+ int ppbaud, pplow, odhpp, odbaud, odstop = 0, i2cbaud, skew;
+ u32 reg;
int ret;
+ ret = pm_runtime_resume_and_get(master->dev);
+ if (ret < 0) {
+ dev_err(master->dev,
+ "<%s> cannot resume i3c bus master, err: %d\n",
+ __func__, ret);
+ return ret;
+ }
+
/* Timings derivation */
fclk_rate = clk_get_rate(master->fclk);
- if (!fclk_rate)
- return -EINVAL;
+ if (!fclk_rate) {
+ ret = -EINVAL;
+ goto rpm_out;
+ }
fclk_period_ns = DIV_ROUND_UP(1000000000, fclk_rate);
/*
- * Using I3C Push-Pull mode, target is 12.5MHz/80ns period.
- * Simplest configuration is using a 50% duty-cycle of 40ns.
+ * Configure for Push-Pull mode.
*/
- ppbaud = DIV_ROUND_UP(40, fclk_period_ns) - 1;
- pplow = 0;
+ if (master->scl_timing.i3c_pp_hi >= I3C_SCL_PP_PERIOD_NS_MIN &&
+ master->scl_timing.i3c_pp_lo >= master->scl_timing.i3c_pp_hi) {
+ ppbaud = DIV_ROUND_UP(master->scl_timing.i3c_pp_hi, fclk_period_ns) - 1;
+ if (ppbaud > SVC_I3C_MAX_PPBAUD)
+ ppbaud = SVC_I3C_MAX_PPBAUD;
+ pplow = DIV_ROUND_UP(master->scl_timing.i3c_pp_lo, fclk_period_ns)
+ - (ppbaud + 1);
+ if (pplow > SVC_I3C_MAX_PPLOW)
+ pplow = SVC_I3C_MAX_PPLOW;
+ bus->scl_rate.i3c = 1000000000 / (((ppbaud + 1) * 2 + pplow) * fclk_period_ns);
+ } else {
+ scl_period_ns = DIV_ROUND_UP(1000000000, bus->scl_rate.i3c);
+ if (bus->scl_rate.i3c == 10000000) {
+ /* Workaround for npcm8xx: 40/60 ns */
+ ppbaud = DIV_ROUND_UP(40, fclk_period_ns) - 1;
+ pplow = DIV_ROUND_UP(20, fclk_period_ns);
+ } else {
+ /* 50% duty-cycle */
+ ppbaud = DIV_ROUND_UP((scl_period_ns / 2), fclk_period_ns) - 1;
+ pplow = 0;
+ }
+ if (ppbaud > SVC_I3C_MAX_PPBAUD)
+ ppbaud = SVC_I3C_MAX_PPBAUD;
+ }
+ pp_high_period_ns = (ppbaud + 1) * fclk_period_ns;
/*
- * Using I3C Open-Drain mode, target is 4.17MHz/240ns with a
- * duty-cycle tuned so that high levels are filetered out by
- * the 50ns filter (target being 40ns).
+ * Configure for Open-Drain mode.
*/
- odhpp = 1;
- high_period_ns = (ppbaud + 1) * fclk_period_ns;
- odbaud = DIV_ROUND_UP(240 - high_period_ns, high_period_ns) - 1;
- od_low_period_ns = (odbaud + 1) * high_period_ns;
-
- switch (bus->mode) {
- case I3C_BUS_MODE_PURE:
- i2cbaud = 0;
- break;
- case I3C_BUS_MODE_MIXED_FAST:
- case I3C_BUS_MODE_MIXED_LIMITED:
- /*
- * Using I2C Fm+ mode, target is 1MHz/1000ns, the difference
- * between the high and low period does not really matter.
- */
- i2cbaud = DIV_ROUND_UP(1000, od_low_period_ns) - 2;
- break;
- case I3C_BUS_MODE_MIXED_SLOW:
- /*
- * Using I2C Fm mode, target is 0.4MHz/2500ns, with the same
- * constraints as the FM+ mode.
- */
- i2cbaud = DIV_ROUND_UP(2500, od_low_period_ns) - 2;
- break;
- default:
- return -EINVAL;
+ if (master->scl_timing.i3c_od_hi >= pp_high_period_ns &&
+ master->scl_timing.i3c_od_lo >= I3C_SCL_OD_LOW_PERIOD_NS_MIN) {
+ if (master->scl_timing.i3c_od_hi == pp_high_period_ns)
+ odhpp = 1;
+ else
+ odhpp = 0;
+ odbaud = DIV_ROUND_UP(master->scl_timing.i3c_od_lo, pp_high_period_ns) - 1;
+ } else {
+ /* Set default OD timing: 1MHz/1000ns with 50% duty cycle */
+ odhpp = 0;
+ odbaud = DIV_ROUND_UP(500, pp_high_period_ns) - 1;
}
+ if (odbaud > SVC_I3C_MAX_ODBAUD)
+ odbaud = SVC_I3C_MAX_ODBAUD;
+ od_low_period_ns = (odbaud + 1) * pp_high_period_ns;
+
+ /* Configure for I2C mode */
+ i2c_period_ns = DIV_ROUND_UP(1000000000, bus->scl_rate.i2c);
+ if (i2c_period_ns < od_low_period_ns * 2)
+ i2c_period_ns = od_low_period_ns * 2;
+ i2cbaud = DIV_ROUND_UP(i2c_period_ns, od_low_period_ns) - 2;
+ if (i2cbaud > SVC_I3C_MAX_I2CBAUD)
+ i2cbaud = SVC_I3C_MAX_I2CBAUD;
+
+ i3c_scl_rate = 1000000000 / (((ppbaud + 1) * 2 + pplow) * fclk_period_ns);
+ i2c_scl_rate = 1000000000 / ((i2cbaud + 2) * od_low_period_ns);
+
+ if (bus->mode != I3C_BUS_MODE_PURE)
+ odstop = 1;
+
+ skew = min(7, ((ppbaud + 1) / 2));
reg = SVC_I3C_MCONFIG_MASTER_EN |
SVC_I3C_MCONFIG_DISTO(0) |
- SVC_I3C_MCONFIG_HKEEP(0) |
- SVC_I3C_MCONFIG_ODSTOP(0) |
+ SVC_I3C_MCONFIG_HKEEP(3) |
+ SVC_I3C_MCONFIG_ODSTOP(odstop) |
SVC_I3C_MCONFIG_PPBAUD(ppbaud) |
SVC_I3C_MCONFIG_PPLOW(pplow) |
SVC_I3C_MCONFIG_ODBAUD(odbaud) |
SVC_I3C_MCONFIG_ODHPP(odhpp) |
- SVC_I3C_MCONFIG_SKEW(0) |
+ SVC_I3C_MCONFIG_SKEW(skew) |
SVC_I3C_MCONFIG_I2CBAUD(i2cbaud);
writel(reg, master->regs + SVC_I3C_MCONFIG);
+ dev_dbg(master->dev, "dts: i3c rate=%lu, i2c rate=%lu\n",
+ bus->scl_rate.i3c, bus->scl_rate.i2c);
+ dev_info(master->dev, "fclk=%lu, period_ns=%lu\n", fclk_rate, fclk_period_ns);
+ dev_info(master->dev, "i3c scl_rate=%lu\n", i3c_scl_rate);
+ dev_info(master->dev, "i2c scl_rate=%lu\n", i2c_scl_rate);
+ dev_info(master->dev, "pp_high=%u, pp_low=%lu\n", pp_high_period_ns,
+ (ppbaud + 1 + pplow) * fclk_period_ns);
+ dev_info(master->dev, "od_high=%d, od_low=%d\n", odhpp ? pp_high_period_ns : od_low_period_ns,
+ od_low_period_ns);
+ dev_dbg(master->dev, "i2c_high=%u, i2c_low=%u\n", ((i2cbaud >> 1) + 1) * od_low_period_ns,
+ ((i2cbaud >> 1) + 1 + (i2cbaud % 2)) * od_low_period_ns);
+ dev_dbg(master->dev, "ppbaud=%d, pplow=%d, odbaud=%d, i2cbaud=%d, skew=%d\n",
+ ppbaud, pplow, odbaud, i2cbaud, skew);
+ dev_info(master->dev, "mconfig=0x%x\n", readl(master->regs + SVC_I3C_MCONFIG));
/* Master core's registration */
ret = i3c_master_get_free_addr(m, 0);
if (ret < 0)
- return ret;
+ goto rpm_out;
info.dyn_addr = ret;
+ reg = readl(master->regs + SVC_I3C_VENDORID);
+ info.pid = (SVC_I3C_VENDORID_VID(reg) << 33 ) | readl(master->regs + SVC_I3C_PARTNO);
writel(SVC_MDYNADDR_VALID | SVC_MDYNADDR_ADDR(info.dyn_addr),
master->regs + SVC_I3C_MDYNADDR);
ret = i3c_master_set_info(&master->base, &info);
if (ret)
- return ret;
+ goto rpm_out;
- svc_i3c_master_enable_interrupts(master, SVC_I3C_MINT_SLVSTART);
+rpm_out:
+ pm_runtime_mark_last_busy(master->dev);
+ pm_runtime_put_autosuspend(master->dev);
- return 0;
+ return ret;
}
static void svc_i3c_master_bus_cleanup(struct i3c_master_controller *m)
{
struct svc_i3c_master *master = to_svc_i3c_master(m);
+ int ret;
+
+ ret = pm_runtime_resume_and_get(master->dev);
+ if (ret < 0) {
+ dev_err(master->dev, "<%s> Cannot get runtime PM.\n", __func__);
+ return;
+ }
svc_i3c_master_disable_interrupts(master);
/* Disable master */
writel(0, master->regs + SVC_I3C_MCONFIG);
+
+ pm_runtime_mark_last_busy(master->dev);
+ pm_runtime_put_autosuspend(master->dev);
}
static int svc_i3c_master_reserve_slot(struct svc_i3c_master *master)
@@ -679,8 +1115,10 @@ static int svc_i3c_master_readb(struct svc_i3c_master *master, u8 *dst,
u32 reg;
for (i = 0; i < len; i++) {
- ret = readl_poll_timeout(master->regs + SVC_I3C_MSTATUS, reg,
- SVC_I3C_MSTATUS_RXPEND(reg), 0, 1000);
+ ret = readl_poll_timeout_atomic(master->regs + SVC_I3C_MSTATUS,
+ reg,
+ SVC_I3C_MSTATUS_RXPEND(reg),
+ 0, 1000);
if (ret)
return ret;
@@ -695,14 +1133,18 @@ static int svc_i3c_master_do_daa_locked(struct svc_i3c_master *master,
{
u64 prov_id[SVC_I3C_MAX_DEVS] = {}, nacking_prov_id = 0;
unsigned int dev_nb = 0, last_addr = 0;
+ unsigned long start = jiffies;
u32 reg;
int ret, i;
+ int dyn_addr;
+
+ svc_i3c_master_flush_fifo(master);
while (true) {
/* Enter/proceed with DAA */
writel(SVC_I3C_MCTRL_REQUEST_PROC_DAA |
SVC_I3C_MCTRL_TYPE_I3C |
- SVC_I3C_MCTRL_IBIRESP_NACK |
+ SVC_I3C_MCTRL_IBIRESP_MANUAL |
SVC_I3C_MCTRL_DIR(SVC_I3C_MCTRL_DIR_WRITE),
master->regs + SVC_I3C_MCTRL);
@@ -710,16 +1152,44 @@ static int svc_i3c_master_do_daa_locked(struct svc_i3c_master *master,
* Either one slave will send its ID, or the assignment process
* is done.
*/
- ret = readl_poll_timeout(master->regs + SVC_I3C_MSTATUS, reg,
- SVC_I3C_MSTATUS_RXPEND(reg) |
- SVC_I3C_MSTATUS_MCTRLDONE(reg),
- 1, 1000);
+ ret = readl_relaxed_poll_timeout_atomic(master->regs + SVC_I3C_MSTATUS,
+ reg,
+ SVC_I3C_MSTATUS_RXPEND(reg) |
+ SVC_I3C_MSTATUS_MCTRLDONE(reg),
+ 0, 1000);
if (ret)
return ret;
+ if (time_after(jiffies, start + msecs_to_jiffies(3000))) {
+ svc_i3c_master_emit_stop(master);
+ dev_info(master->dev, "do_daa expired\n");
+ break;
+ }
+ /* runtime do_daa may ibiwon by others slave devices */
+ if (SVC_I3C_MSTATUS_IBIWON(reg)) {
+ ret = svc_i3c_master_handle_ibiwon(master, false);
+ if (ret) {
+ dev_err(master->dev, "daa: handle ibi event fail, ret=%d\n", ret);
+ return ret;
+ }
+ writel(SVC_I3C_MINT_MCTRLDONE, master->regs + SVC_I3C_MSTATUS);
+ continue;
+ }
+
+ if (dev_nb == SVC_I3C_MAX_DEVS) {
+ svc_i3c_master_emit_stop(master);
+ dev_info(master->dev, "Reach max devs\n");
+ break;
+ }
if (SVC_I3C_MSTATUS_RXPEND(reg)) {
u8 data[6];
+ /* Give the slave device a suitable dynamic address */
+ dyn_addr = i3c_master_get_free_addr(&master->base, last_addr + 1);
+ if (dyn_addr < 0)
+ return dyn_addr;
+ writel(dyn_addr, master->regs + SVC_I3C_MWDATAB);
+
/*
* We only care about the 48-bit provisional ID yet to
* be sure a device does not nack an address twice.
@@ -737,8 +1207,13 @@ static int svc_i3c_master_do_daa_locked(struct svc_i3c_master *master,
if (ret)
return ret;
} else if (SVC_I3C_MSTATUS_MCTRLDONE(reg)) {
- if (SVC_I3C_MSTATUS_STATE_IDLE(reg) &&
+ if ((SVC_I3C_MSTATUS_STATE_IDLE(reg) |
+ SVC_I3C_MSTATUS_STATE_SLVREQ(reg)) &&
SVC_I3C_MSTATUS_COMPLETE(reg)) {
+ /*
+ * Sometimes the controller state is SLVREQ after
+ * DAA request completed, treat it as normal end.
+ */
/*
* All devices received and acked they dynamic
* address, this is the natural end of the DAA
@@ -747,8 +1222,10 @@ static int svc_i3c_master_do_daa_locked(struct svc_i3c_master *master,
break;
} else if (SVC_I3C_MSTATUS_NACKED(reg)) {
/* No I3C devices attached */
- if (dev_nb == 0)
+ if (dev_nb == 0) {
+ svc_i3c_master_emit_stop(master);
break;
+ }
/*
* A slave device nacked the address, this is
@@ -771,24 +1248,18 @@ static int svc_i3c_master_do_daa_locked(struct svc_i3c_master *master,
}
/* Wait for the slave to be ready to receive its address */
- ret = readl_poll_timeout(master->regs + SVC_I3C_MSTATUS, reg,
- SVC_I3C_MSTATUS_MCTRLDONE(reg) &&
- SVC_I3C_MSTATUS_STATE_DAA(reg) &&
- SVC_I3C_MSTATUS_BETWEEN(reg),
- 0, 1000);
+ ret = readl_poll_timeout_atomic(master->regs + SVC_I3C_MSTATUS,
+ reg,
+ SVC_I3C_MSTATUS_MCTRLDONE(reg) &&
+ SVC_I3C_MSTATUS_STATE_DAA(reg) &&
+ SVC_I3C_MSTATUS_BETWEEN(reg),
+ 0, 1000);
if (ret)
return ret;
- /* Give the slave device a suitable dynamic address */
- ret = i3c_master_get_free_addr(&master->base, last_addr + 1);
- if (ret < 0)
- return ret;
-
- addrs[dev_nb] = ret;
+ addrs[dev_nb] = dyn_addr;
dev_dbg(master->dev, "DAA: device %d assigned to 0x%02x\n",
dev_nb, addrs[dev_nb]);
-
- writel(addrs[dev_nb], master->regs + SVC_I3C_MWDATAB);
last_addr = addrs[dev_nb++];
}
@@ -807,8 +1278,10 @@ static int svc_i3c_update_ibirules(struct svc_i3c_master *master)
/* Create the IBIRULES register for both cases */
i3c_bus_for_each_i3cdev(&master->base.bus, dev) {
- if (I3C_BCR_DEVICE_ROLE(dev->info.bcr) == I3C_BCR_I3C_MASTER)
- continue;
+ if (I3C_BCR_DEVICE_ROLE(dev->info.bcr) == I3C_BCR_I3C_MASTER) {
+ if (!(dev->info.bcr & I3C_BCR_IBI_REQ_CAP))
+ continue;
+ }
if (dev->info.bcr & I3C_BCR_IBI_PAYLOAD) {
reg_mbyte |= SVC_I3C_IBIRULES_ADDR(mbyte_addr_ok,
@@ -855,35 +1328,47 @@ static int svc_i3c_master_do_daa(struct i3c_master_controller *m)
{
struct svc_i3c_master *master = to_svc_i3c_master(m);
u8 addrs[SVC_I3C_MAX_DEVS];
- unsigned long flags;
unsigned int dev_nb;
+ unsigned long flags;
int ret, i;
- spin_lock_irqsave(&master->xferqueue.lock, flags);
+ ret = pm_runtime_resume_and_get(master->dev);
+ if (ret < 0) {
+ dev_err(master->dev, "<%s> Cannot get runtime PM.\n", __func__);
+ return ret;
+ }
+
+ mutex_lock(&master->lock);
+ spin_lock_irqsave(&master->req_lock, flags);
ret = svc_i3c_master_do_daa_locked(master, addrs, &dev_nb);
- spin_unlock_irqrestore(&master->xferqueue.lock, flags);
- if (ret)
- goto emit_stop;
+ spin_unlock_irqrestore(&master->req_lock, flags);
+ mutex_unlock(&master->lock);
+ if (ret) {
+ svc_i3c_master_emit_stop(master);
+ svc_i3c_master_clear_merrwarn(master);
+ goto rpm_out;
+ }
/* Register all devices who participated to the core */
for (i = 0; i < dev_nb; i++) {
ret = i3c_master_add_i3c_dev_locked(m, addrs[i]);
if (ret)
- return ret;
+ dev_err(master->dev, "Unable to add i3c dev@0x%x, err %d\n",
+ addrs[i], ret);
}
/* Configure IBI auto-rules */
ret = svc_i3c_update_ibirules(master);
- if (ret) {
+ if (ret)
dev_err(master->dev, "Cannot handle such a list of devices");
- return ret;
- }
- return 0;
+rpm_out:
+ pm_runtime_mark_last_busy(master->dev);
+ pm_runtime_put_autosuspend(master->dev);
-emit_stop:
- svc_i3c_master_emit_stop(master);
- svc_i3c_master_clear_merrwarn(master);
+ /* No Slave ACK */
+ if (ret == -EIO)
+ return 0;
return ret;
}
@@ -891,27 +1376,36 @@ static int svc_i3c_master_do_daa(struct i3c_master_controller *m)
static int svc_i3c_master_read(struct svc_i3c_master *master,
u8 *in, unsigned int len)
{
- int offset = 0, i, ret;
- u32 mdctrl;
+ int offset = 0, i;
+ u32 mdctrl, mstatus;
+ bool completed = false;
+ unsigned int count;
+ ktime_t timeout;
- while (offset < len) {
- unsigned int count;
+ timeout = ktime_add_ms(ktime_get(), 1000);
+ while (!completed) {
+ mstatus = readl(master->regs + SVC_I3C_MSTATUS);
+ if (SVC_I3C_MSTATUS_COMPLETE(mstatus) != 0)
+ completed = true;
- ret = readl_poll_timeout(master->regs + SVC_I3C_MDATACTRL,
- mdctrl,
- !(mdctrl & SVC_I3C_MDATACTRL_RXEMPTY),
- 0, 1000);
- if (ret)
- return ret;
+ if (ktime_compare(ktime_get(), timeout) > 0) {
+ dev_err(master->dev, "I3C read timeout, count=%d/%d\n", offset, len);
+ return -ETIMEDOUT;
+ }
+ mdctrl = readl(master->regs + SVC_I3C_MDATACTRL);
count = SVC_I3C_MDATACTRL_RXCOUNT(mdctrl);
+ if (offset + count > len) {
+ dev_err(master->dev, "I3C receive length too long!\n");
+ return -EINVAL;
+ }
for (i = 0; i < count; i++)
in[offset + i] = readl(master->regs + SVC_I3C_MRDATAB);
offset += count;
}
- return 0;
+ return offset;
}
static int svc_i3c_master_write(struct svc_i3c_master *master,
@@ -941,67 +1435,340 @@ static int svc_i3c_master_write(struct svc_i3c_master *master,
return 0;
}
+static void svc_i3c_master_stop_dma(struct svc_i3c_master *master)
+{
+ writel(0, master->dma_regs + NPCM_GDMA_CTL(DMA_CH_TX));
+ writel(0, master->dma_regs + NPCM_GDMA_CTL(DMA_CH_RX));
+ writel(0, master->regs + SVC_I3C_MDMACTRL);
+
+ /* Disable COMPLETE interrupt */
+ writel(SVC_I3C_MINT_COMPLETE, master->regs + SVC_I3C_MINTCLR);
+ master->dma_started = false;
+}
+
+static void svc_i3c_master_write_dma_table(const u8 *src, u32 *dst, int len)
+{
+ int i;
+
+ if (len > MAX_DMA_COUNT)
+ return;
+
+ for (i = 0; i < len; i++)
+ dst[i] = (u32)src[i] & 0xFF;
+
+ /* Set end bit for last byte */
+ dst[len - 1] |= 0x100;
+}
+
+static int svc_i3c_master_start_dma(struct svc_i3c_master *master)
+{
+ struct npcm_dma_xfer_desc *xfer = &master->dma_xfer;
+ int ch = xfer->rnw ? DMA_CH_RX : DMA_CH_TX;
+ u32 val;
+
+ if (!xfer->len)
+ return 0;
+
+ dev_dbg(master->dev, "start dma for %s, count %d\n",
+ xfer->rnw ? "R" : "W", xfer->len);
+
+ /* Set DMA transfer count */
+ writel(xfer->len, master->dma_regs + NPCM_GDMA_TCNT(ch));
+
+ /* Write data to DMA TX table */
+ if (!xfer->rnw)
+ svc_i3c_master_write_dma_table(xfer->out,
+ (u32 *)master->dma_tx_buf,
+ xfer->len);
+
+
+ /*
+ * Setup I3C DMA control
+ * 1 byte DMA width
+ * Enable DMA util dsiabled
+ */
+ val = SVC_I3C_MDMACTRL_DMAWIDTH(1);
+ val |= xfer->rnw ? SVC_I3C_MDMACTRL_DMAFB(2) : SVC_I3C_MDMACTRL_DMATB(2);
+ writel(val, master->regs + SVC_I3C_MDMACTRL);
+
+ /*
+ * Enable DMA
+ * Source Address Fixed for RX
+ * Destination Address Fixed for TX
+ * Use 32-bit transfer width for TX (queal to MWDATAB register width)
+ */
+ val = NPCM_GDMA_CTL_GDMAEN;
+ if (xfer->rnw)
+ val |= NPCM_GDMA_CTL_SAFIX | NPCM_GDMA_CTL_GDMAMS(2);
+ else
+ val |= NPCM_GDMA_CTL_DAFIX | NPCM_GDMA_CTL_GDMAMS(1) | NPCM_GDMA_CTL_TWS(2);
+ writel(val, master->dma_regs + NPCM_GDMA_CTL(ch));
+ master->dma_started = true;
+
+ return 0;
+}
+
+static int svc_i3c_master_wait_for_complete(struct svc_i3c_master *master)
+{
+ struct npcm_dma_xfer_desc *xfer = &master->dma_xfer;
+ int ch = xfer->rnw ? DMA_CH_RX : DMA_CH_TX;
+ u32 count;
+ int ret;
+
+ ret = wait_for_completion_timeout(&master->xfer_comp, msecs_to_jiffies(100));
+ if (!ret) {
+ svc_i3c_master_stop_dma(master);
+ dev_err(master->dev, "DMA transfer timeout (%s)\n", xfer->rnw ? "Read" : "write");
+ dev_err(master->dev, "mstatus = 0x%02x\n", readl(master->regs + SVC_I3C_MSTATUS));
+ svc_i3c_master_err_stats(master, ETIMEDOUT);
+ return -ETIMEDOUT;
+ }
+
+ /* Get the DMA transfer count */
+ count = readl(master->dma_regs + NPCM_GDMA_CTCNT(ch));
+ count = (count > xfer->len) ? 0 :
+ (xfer->len - count);
+ dev_dbg(master->dev, "dma xfer count %u\n", count);
+ if (xfer->rnw)
+ memcpy(xfer->in, master->dma_rx_buf, count);
+ if (count != xfer->len)
+ dev_dbg(master->dev, "short dma xfer(%s), want %d transfer %d\n",
+ xfer->rnw ? "R" : "W", xfer->len, count);
+
+ svc_i3c_master_stop_dma(master);
+
+ return count;
+}
+
static int svc_i3c_master_xfer(struct svc_i3c_master *master,
bool rnw, unsigned int xfer_type, u8 addr,
u8 *in, const u8 *out, unsigned int xfer_len,
- unsigned int read_len, bool continued)
+ unsigned int *read_len, bool continued,
+ bool use_dma)
{
- u32 reg;
- int ret;
+ u32 reg, rdterm = *read_len, mstatus;
+ int ret, i, count, space;
+ unsigned long flags;
+ unsigned long start;
+ u32 ibiresp;
+
+ if (rdterm > SVC_I3C_MAX_RDTERM)
+ rdterm = SVC_I3C_MAX_RDTERM;
+
+ /* Use SDR mode if transfer size is odd */
+ if (xfer_type == SVC_I3C_MCTRL_TYPE_I3C_DDR && (xfer_len % 2))
+ xfer_type = SVC_I3C_MCTRL_TYPE_I3C;
+
+ if (xfer_type == SVC_I3C_MCTRL_TYPE_I3C_DDR) {
+ /* Write the HDR-DDR cmd to the MWDATAB register to send out to slave */
+ writel(HDR_COMMAND, master->regs + SVC_I3C_MWDATAB);
+ /* Read count: add 1 for HDR-DDR command word and 1 for CRC word */
+ if (rnw)
+ rdterm = 2 + rdterm / 2;
+ master->hdr_mode = true;
+ }
+ /*
+ * There is a chance that first tx data bit is lost when it
+ * is not ready in FIFO right after address phase.
+ * Prepare data before starting the transfer to fix this problem.
+ */
+ if (!rnw && xfer_len && !use_dma) {
+ ret = readl_poll_timeout(master->regs + SVC_I3C_MDATACTRL,
+ reg,
+ !(reg & SVC_I3C_MDATACTRL_TXFULL),
+ 0, 1000);
+ if (ret)
+ return ret;
- /* clean SVC_I3C_MINT_IBIWON w1c bits */
- writel(SVC_I3C_MINT_IBIWON, master->regs + SVC_I3C_MSTATUS);
+ reg = readl(master->regs + SVC_I3C_MDATACTRL);
+ space = SVC_I3C_FIFO_SIZE - SVC_I3C_MDATACTRL_TXCOUNT(reg);
+ count = xfer_len > space ? space : xfer_len;
+ for (i = 0; i < count; i++) {
+ if (i == xfer_len - 1)
+ writel(out[0], master->regs + SVC_I3C_MWDATABE);
+ else
+ writel(out[0], master->regs + SVC_I3C_MWDATAB);
+ out++;
+ }
+ xfer_len -= count;
+ }
+ /* Prevent fifo operation from delay by interrupt */
+ if (!use_dma)
+ local_irq_disable();
+
+ /* Prevent DMA start while IBI isr is running */
+ spin_lock_irqsave(&master->req_lock, flags);
+ if (use_dma) {
+ if (xfer_len > MAX_DMA_COUNT) {
+ dev_err(master->dev, "data is larger than buffer size (%d)\n",
+ MAX_DMA_COUNT);
+ spin_unlock_irqrestore(&master->req_lock, flags);
+ return -EINVAL;
+ }
+ master->dma_xfer.out = out;
+ master->dma_xfer.in = in;
+ master->dma_xfer.len = xfer_len;
+ master->dma_xfer.rnw = rnw;
+ master->dma_xfer.end = !continued;
+ init_completion(&master->xfer_comp);
+ svc_i3c_master_start_dma(master);
+ }
+ start = jiffies;
+ /*
+ * IBI payload size may be larger than rdterm, use manual IBI response
+ * for read operation to set the proper RDTERM value in IBI ack request.
+ */
+ if (master->probe_done)
+ ibiresp = rnw ? SVC_I3C_MCTRL_IBIRESP_MANUAL : SVC_I3C_MCTRL_IBIRESP_AUTO;
+ else
+ ibiresp = SVC_I3C_MCTRL_IBIRESP_MANUAL;
+retry_start:
writel(SVC_I3C_MCTRL_REQUEST_START_ADDR |
xfer_type |
- SVC_I3C_MCTRL_IBIRESP_NACK |
+ ibiresp |
SVC_I3C_MCTRL_DIR(rnw) |
SVC_I3C_MCTRL_ADDR(addr) |
- SVC_I3C_MCTRL_RDTERM(read_len),
+ SVC_I3C_MCTRL_RDTERM(rdterm),
master->regs + SVC_I3C_MCTRL);
ret = readl_poll_timeout(master->regs + SVC_I3C_MSTATUS, reg,
SVC_I3C_MSTATUS_MCTRLDONE(reg), 0, 1000);
if (ret)
- goto emit_stop;
+ goto emit_stop_locked;
- /*
- * According to I3C spec ver 1.1.1, 5.1.2.2.3 Consequence of Controller Starting a Frame
- * with I3C Target Address.
- *
- * The I3C Controller normally should start a Frame, the Address may be arbitrated, and so
- * the Controller shall monitor to see whether an In-Band Interrupt request, a Controller
- * Role Request (i.e., Secondary Controller requests to become the Active Controller), or
- * a Hot-Join Request has been made.
- *
- * If missed IBIWON check, the wrong data will be return. When IBIWON happen, return failure
- * and yield the above events handler.
- */
- if (SVC_I3C_MSTATUS_IBIWON(reg)) {
- ret = -ENXIO;
+ mstatus = readl(master->regs + SVC_I3C_MSTATUS);
+ if (SVC_I3C_MSTATUS_IBIWON(mstatus)) {
+ /*
+ * Unable to handle slave event before driver probe done.
+ * Ignore the event and disable slave interrupts
+ * (send a Repeated START and DISEC CCC).
+ */
+ if (!master->probe_done) {
+ if (use_dma)
+ svc_i3c_master_stop_dma(master);
+ /* ACK the IBI and drop the payload */
+ svc_i3c_master_ack_ibi(master, true);
+ readl_poll_timeout(master->regs + SVC_I3C_MSTATUS, reg,
+ SVC_I3C_MSTATUS_COMPLETE(reg), 0, 1000);
+ svc_i3c_master_flush_fifo(master);
+ /* Send a Repeated Start followed by a DISEC CCC */
+ writel(SVC_I3C_MCTRL_REQUEST_START_ADDR |
+ xfer_type | SVC_I3C_MCTRL_IBIRESP_NACK |
+ SVC_I3C_MCTRL_DIR(0) |
+ SVC_I3C_MCTRL_ADDR(I3C_BROADCAST_ADDR),
+ master->regs + SVC_I3C_MCTRL);
+ writel(I3C_CCC_DISEC(true), master->regs + SVC_I3C_MWDATAB);
+ writel(I3C_CCC_EVENT_SIR | I3C_CCC_EVENT_MR | I3C_CCC_EVENT_HJ,
+ master->regs + SVC_I3C_MWDATABE);
+ readl_poll_timeout(master->regs + SVC_I3C_MSTATUS, reg,
+ SVC_I3C_MSTATUS_COMPLETE(reg), 0, 1000);
+ svc_i3c_master_emit_stop(master);
+ writel(SVC_I3C_MINT_IBIWON, master->regs + SVC_I3C_MSTATUS);
+ spin_unlock_irqrestore(&master->req_lock, flags);
+ if (!use_dma)
+ local_irq_enable();
+ /* Return EAGAIN to restart the transaction */
+ return -EAGAIN;
+ }
+ /* Stop RX DMA to prevent it from receving the ibi payload */
+ if (use_dma && rnw)
+ svc_i3c_master_stop_dma(master);
+ ret = svc_i3c_master_handle_ibiwon(master, !rnw);
+ if (ret) {
+ dev_err(master->dev, "xfer(rnw %d): handle ibi event fail, ret=%d\n",
+ rnw, ret);
+ goto emit_stop_locked;
+ }
+ if (rnw)
+ master->rd_ibiwon_cnt++;
+ else
+ master->wr_ibiwon_cnt++;
+ if (time_after(jiffies, start + msecs_to_jiffies(1000))) {
+ dev_info(master->dev, "abnormal ibiwon events\n");
+ goto emit_stop_locked;
+ }
+
+ if (use_dma && rnw)
+ svc_i3c_master_start_dma(master);
+
+ /* Clear COMPLETE status of this IBI transaction */
+ writel(SVC_I3C_MINT_COMPLETE, master->regs + SVC_I3C_MSTATUS);
+ goto retry_start;
+ }
+ /* Use COMPLETE interrupt as notification of transfer completion */
+ if (use_dma)
+ svc_i3c_master_enable_interrupts(master, SVC_I3C_MINT_COMPLETE);
+ spin_unlock_irqrestore(&master->req_lock, flags);
+
+ reg = readl(master->regs + SVC_I3C_MSTATUS);
+ if (SVC_I3C_MSTATUS_NACKED(reg)) {
+ dev_dbg(master->dev, "addr 0x%x NACK\n", addr);
+ ret = -EIO;
goto emit_stop;
}
- if (rnw)
+ if (use_dma)
+ ret = svc_i3c_master_wait_for_complete(master);
+ else if (rnw)
ret = svc_i3c_master_read(master, in, xfer_len);
else
ret = svc_i3c_master_write(master, out, xfer_len);
- if (ret)
+ if (ret < 0)
goto emit_stop;
- ret = readl_poll_timeout(master->regs + SVC_I3C_MSTATUS, reg,
- SVC_I3C_MSTATUS_COMPLETE(reg), 0, 1000);
- if (ret)
- goto emit_stop;
+ if (rnw)
+ *read_len = ret;
- if (!continued)
+ if (!use_dma) {
+ ret = readl_poll_timeout(master->regs + SVC_I3C_MSTATUS, reg,
+ SVC_I3C_MSTATUS_COMPLETE(reg), 0, 1000);
+ if (ret)
+ goto emit_stop;
+
+ /* If use_dma, COMPLETE bit is cleared in the isr */
+ writel(SVC_I3C_MINT_COMPLETE, master->regs + SVC_I3C_MSTATUS);
+ }
+
+
+ if (master->hdr_mode) {
+ reg = readl(master->regs + SVC_I3C_MERRWARN);
+ if (SVC_I3C_MERRWARN_HCRC(reg)) {
+ dev_err(master->dev, "HDR CRC error\n");
+ ret = -EIO;
+ goto emit_stop;
+ }
+ }
+
+ if (!continued && !use_dma)
svc_i3c_master_emit_stop(master);
+ if (!use_dma)
+ local_irq_enable();
+
return 0;
emit_stop:
+ spin_lock_irqsave(&master->req_lock, flags);
+emit_stop_locked:
+ if (master->dma_started)
+ svc_i3c_master_stop_dma(master);
+ /*
+ * If the read transfer is not completed, update RDTERM value to
+ * terminate the transfer and let emitting STOP work normally.
+ */
+ if (rnw && ret == -ETIMEDOUT) {
+ writel(SVC_I3C_MCTRL_RDTERM(1), master->regs + SVC_I3C_MCTRL);
+ svc_i3c_master_flush_fifo(master);
+ readl_poll_timeout(master->regs + SVC_I3C_MSTATUS, reg,
+ SVC_I3C_MSTATUS_COMPLETE(reg), 0, 1000);
+ }
svc_i3c_master_emit_stop(master);
svc_i3c_master_clear_merrwarn(master);
+ svc_i3c_master_flush_fifo(master);
+ spin_unlock_irqrestore(&master->req_lock, flags);
+ if (!use_dma)
+ local_irq_enable();
return ret;
}
@@ -1039,28 +1806,34 @@ static void svc_i3c_master_dequeue_xfer_locked(struct svc_i3c_master *master,
static void svc_i3c_master_dequeue_xfer(struct svc_i3c_master *master,
struct svc_i3c_xfer *xfer)
{
- unsigned long flags;
-
- spin_lock_irqsave(&master->xferqueue.lock, flags);
svc_i3c_master_dequeue_xfer_locked(master, xfer);
- spin_unlock_irqrestore(&master->xferqueue.lock, flags);
}
static void svc_i3c_master_start_xfer_locked(struct svc_i3c_master *master)
{
struct svc_i3c_xfer *xfer = master->xferqueue.cur;
+ unsigned long flags;
+ int retry = 2;
int ret, i;
if (!xfer)
return;
+ /* Prevent fifo flush while IBI isr is running */
+ spin_lock_irqsave(&master->req_lock, flags);
+ svc_i3c_master_clear_merrwarn(master);
+ svc_i3c_master_flush_fifo(master);
+ spin_unlock_irqrestore(&master->req_lock, flags);
+
for (i = 0; i < xfer->ncmds; i++) {
struct svc_i3c_cmd *cmd = &xfer->cmds[i];
-
+again:
ret = svc_i3c_master_xfer(master, cmd->rnw, xfer->type,
cmd->addr, cmd->in, cmd->out,
- cmd->len, cmd->read_len,
- cmd->continued);
+ cmd->len, &cmd->read_len,
+ cmd->continued, cmd->use_dma);
+ if (ret == -EAGAIN && --retry)
+ goto again;
if (ret)
break;
}
@@ -1084,17 +1857,25 @@ static void svc_i3c_master_start_xfer_locked(struct svc_i3c_master *master)
static void svc_i3c_master_enqueue_xfer(struct svc_i3c_master *master,
struct svc_i3c_xfer *xfer)
{
- unsigned long flags;
+ int ret;
+
+ ret = pm_runtime_resume_and_get(master->dev);
+ if (ret < 0) {
+ dev_err(master->dev, "<%s> Cannot get runtime PM.\n", __func__);
+ return;
+ }
init_completion(&xfer->comp);
- spin_lock_irqsave(&master->xferqueue.lock, flags);
+
if (master->xferqueue.cur) {
list_add_tail(&xfer->node, &master->xferqueue.list);
} else {
master->xferqueue.cur = xfer;
svc_i3c_master_start_xfer_locked(master);
}
- spin_unlock_irqrestore(&master->xferqueue.lock, flags);
+
+ pm_runtime_mark_last_busy(master->dev);
+ pm_runtime_put_autosuspend(master->dev);
}
static bool
@@ -1192,6 +1973,9 @@ static int svc_i3c_master_send_direct_ccc_cmd(struct svc_i3c_master *master,
svc_i3c_master_dequeue_xfer(master, xfer);
mutex_unlock(&master->lock);
+ if (cmd->read_len != xfer_len)
+ ccc->dests[0].payload.len = cmd->read_len;
+
ret = xfer->ret;
svc_i3c_master_free_xfer(xfer);
@@ -1210,8 +1994,11 @@ static int svc_i3c_master_send_ccc_cmd(struct i3c_master_controller *m,
else
ret = svc_i3c_master_send_direct_ccc_cmd(master, cmd);
- if (ret)
+ if (ret) {
+ dev_dbg(master->dev, "send ccc 0x%02x %s, ret = %d\n",
+ cmd->id, broadcast ? "(broadcast)" : "", ret);
cmd->err = I3C_ERROR_M2;
+ }
return ret;
}
@@ -1230,7 +2017,10 @@ static int svc_i3c_master_priv_xfers(struct i3c_dev_desc *dev,
if (!xfer)
return -ENOMEM;
- xfer->type = SVC_I3C_MCTRL_TYPE_I3C;
+ if (master->hdr_ddr && dev->info.hdr_cap & BIT(I3C_HDR_DDR))
+ xfer->type = SVC_I3C_MCTRL_TYPE_I3C_DDR;
+ else
+ xfer->type = SVC_I3C_MCTRL_TYPE_I3C;
for (i = 0; i < nxfers; i++) {
struct svc_i3c_cmd *cmd = &xfer->cmds[i];
@@ -1242,6 +2032,8 @@ static int svc_i3c_master_priv_xfers(struct i3c_dev_desc *dev,
cmd->len = xfers[i].len;
cmd->read_len = xfers[i].rnw ? xfers[i].len : 0;
cmd->continued = (i + 1) < nxfers;
+ if (master->use_dma && xfers[i].len > 1)
+ cmd->use_dma = true;
}
mutex_lock(&master->lock);
@@ -1250,6 +2042,12 @@ static int svc_i3c_master_priv_xfers(struct i3c_dev_desc *dev,
svc_i3c_master_dequeue_xfer(master, xfer);
mutex_unlock(&master->lock);
+ for (i = 0; i < nxfers; i++) {
+ struct svc_i3c_cmd *cmd = &xfer->cmds[i];
+
+ if (xfers[i].rnw)
+ xfers[i].len = cmd->read_len;
+ }
ret = xfer->ret;
svc_i3c_master_free_xfer(xfer);
@@ -1305,9 +2103,9 @@ static int svc_i3c_master_request_ibi(struct i3c_dev_desc *dev,
unsigned long flags;
unsigned int i;
- if (dev->ibi->max_payload_len > SVC_I3C_FIFO_SIZE) {
+ if (dev->ibi->max_payload_len > SVC_I3C_MAX_IBI_PAYLOAD_SIZE) {
dev_err(master->dev, "IBI max payload %d should be < %d\n",
- dev->ibi->max_payload_len, SVC_I3C_FIFO_SIZE);
+ dev->ibi->max_payload_len, SVC_I3C_MAX_IBI_PAYLOAD_SIZE + 1);
return -ERANGE;
}
@@ -1352,6 +2150,18 @@ static void svc_i3c_master_free_ibi(struct i3c_dev_desc *dev)
static int svc_i3c_master_enable_ibi(struct i3c_dev_desc *dev)
{
struct i3c_master_controller *m = i3c_dev_get_master(dev);
+ struct svc_i3c_master *master = to_svc_i3c_master(m);
+ int ret;
+
+ ret = pm_runtime_resume_and_get(master->dev);
+ if (ret < 0) {
+ dev_err(master->dev, "<%s> Cannot get runtime PM.\n", __func__);
+ return ret;
+ }
+
+ /* Clear the interrupt status */
+ writel(SVC_I3C_MINT_SLVSTART, master->regs + SVC_I3C_MSTATUS);
+ svc_i3c_master_enable_interrupts(master, SVC_I3C_MINT_SLVSTART);
return i3c_master_enec_locked(m, dev->info.dyn_addr, I3C_CCC_EVENT_SIR);
}
@@ -1359,8 +2169,17 @@ static int svc_i3c_master_enable_ibi(struct i3c_dev_desc *dev)
static int svc_i3c_master_disable_ibi(struct i3c_dev_desc *dev)
{
struct i3c_master_controller *m = i3c_dev_get_master(dev);
+ struct svc_i3c_master *master = to_svc_i3c_master(m);
+ int ret;
+
+ writel(SVC_I3C_MINT_SLVSTART, master->regs + SVC_I3C_MINTCLR);
+
+ ret = i3c_master_disec_locked(m, dev->info.dyn_addr, I3C_CCC_EVENT_SIR);
- return i3c_master_disec_locked(m, dev->info.dyn_addr, I3C_CCC_EVENT_SIR);
+ pm_runtime_mark_last_busy(master->dev);
+ pm_runtime_put_autosuspend(master->dev);
+
+ return ret;
}
static void svc_i3c_master_recycle_ibi_slot(struct i3c_dev_desc *dev,
@@ -1391,29 +2210,442 @@ static const struct i3c_master_controller_ops svc_i3c_master_ops = {
.disable_ibi = svc_i3c_master_disable_ibi,
};
-static void svc_i3c_master_reset(struct svc_i3c_master *master)
+static int svc_i3c_master_prepare_clks(struct svc_i3c_master *master)
{
+ int ret = 0;
+
+ ret = clk_prepare_enable(master->pclk);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(master->fclk);
+ if (ret) {
+ clk_disable_unprepare(master->pclk);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(master->sclk);
+ if (ret) {
+ clk_disable_unprepare(master->pclk);
+ clk_disable_unprepare(master->fclk);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void svc_i3c_master_unprepare_clks(struct svc_i3c_master *master)
+{
+ clk_disable_unprepare(master->pclk);
+ clk_disable_unprepare(master->fclk);
+ clk_disable_unprepare(master->sclk);
+}
+
+static void svc_i3c_slave_enable_interrupts(struct svc_i3c_master *master,
+ bool enable)
+{
+ /* Use STOP condition to check the end of transaction */
+ if (enable)
+ writel(SVC_I3C_INT_STOP, master->regs + SVC_I3C_INTSET);
+ else
+ writel(SVC_I3C_INT_STOP, master->regs + SVC_I3C_INTCLR);
+}
+
+static void svc_i3c_slave_stop_dma(struct svc_i3c_master *master)
+{
+ writel(0, master->dma_regs + NPCM_GDMA_CTL(DMA_CH_TX));
+ writel(0, master->dma_regs + NPCM_GDMA_CTL(DMA_CH_RX));
+ writel(0, master->regs + SVC_I3C_MDMACTRL);
+}
+
+static int svc_i3c_slave_start_dma(struct svc_i3c_master *master,
+ struct svc_i3c_xfer *xfer)
+{
+ struct svc_i3c_cmd *cmd = &xfer->cmds[0];
+ int ch = cmd->rnw ? DMA_CH_RX : DMA_CH_TX;
+ u32 val;
+
+ if (!cmd->len)
+ return 0;
+
+ dev_dbg(master->dev, "slave start dma for %s, count %d\n",
+ cmd->rnw ? "R" : "W", cmd->len);
+
+ /* Set DMA transfer count */
+ writel(cmd->len, master->dma_regs + NPCM_GDMA_TCNT(ch));
+
+ /* Write data to DMA TX table */
+ if (ch == DMA_CH_TX)
+ svc_i3c_master_write_dma_table(cmd->out,
+ (u32 *)master->dma_tx_buf,
+ cmd->len);
+
+ /*
+ * Setup I3C DMA control
+ * 1 byte DMA width
+ * Enable DMA util dsiabled
+ */
+ val = SVC_I3C_DMACTRL_DMAWIDTH(1);
+ val |= (ch == DMA_CH_RX) ? SVC_I3C_DMACTRL_DMAFB(2) : SVC_I3C_DMACTRL_DMATB(2);
+ writel(val, master->regs + SVC_I3C_DMACTRL);
+
+ /* Clear STOP status because this will be used as check point of transaction end */
+ writel(SVC_I3C_STATUS_STOP, master->regs + SVC_I3C_STATUS);
+
+ /*
+ * Enable DMA
+ * Source Address Fixed for RX
+ * Destination Address Fixed for TX
+ * Use 32-bit transfer width for TX (queal to MWDATAB register width)
+ */
+ val = NPCM_GDMA_CTL_GDMAEN;
+ if (ch == DMA_CH_RX)
+ val |= NPCM_GDMA_CTL_SAFIX | NPCM_GDMA_CTL_GDMAMS(2);
+ else
+ val |= NPCM_GDMA_CTL_DAFIX | NPCM_GDMA_CTL_GDMAMS(1) | NPCM_GDMA_CTL_TWS(2);
+ writel(val, master->dma_regs + NPCM_GDMA_CTL(ch));
+
+ return 0;
+}
+
+static void svc_i3c_slave_check_complete(struct svc_i3c_master *master)
+{
+ struct svc_i3c_xfer *xfer = master->slave.cur;
+ struct svc_i3c_cmd *cmd = &xfer->cmds[0];
+ int ch = cmd->rnw ? DMA_CH_RX : DMA_CH_TX;
+ u32 count, reg;
+ bool hdr_mode = false;
+
+ /* Get the DMA transfer count */
+ count = readl(master->dma_regs + NPCM_GDMA_CTCNT(ch));
+
+ /* No rx data transferred */
+ if (cmd->rnw && cmd->len == count)
+ return;
+
+ /* No tx data transferred */
+ if (!cmd->rnw) {
+ reg = readl(master->regs + SVC_I3C_DATACTRL);
+ if (cmd->len == count + SVC_I3C_DATACTRL_TXCOUNT(reg))
+ return;
+ }
+ svc_i3c_slave_stop_dma(master);
+
+ if (cmd->len < count)
+ goto quit;
+ count = cmd->len - count;
+
+ reg = readl(master->regs + SVC_I3C_STATUS);
+ if (reg & SVC_I3C_STATUS_DDRMATCH) {
+ writel(SVC_I3C_STATUS_DDRMATCH, master->regs);
+ hdr_mode = true;
+ }
+ if (cmd->rnw) {
+ struct i3c_dev_desc *desc = master->base.this;
+
+ if (hdr_mode) {
+ /* Drop the hdr command */
+ dev_dbg(master->dev, "drop hdr cmd: 0x%x\n", master->dma_rx_buf[0]);
+ count--;
+ memcpy(cmd->in, master->dma_rx_buf + 1, count);
+ } else {
+ memcpy(cmd->in, master->dma_rx_buf, count);
+ }
+ dev_dbg(master->dev, "slave rx count %u\n", count);
+ if (desc->target_info.read_handler)
+ desc->target_info.read_handler(desc->dev, cmd->in, count);
+ } else {
+ cmd->len = count - SVC_I3C_DATACTRL_TXCOUNT(reg);
+ if (hdr_mode) {
+ reg = readl(master->regs + SVC_I3C_RDATAB);
+ dev_dbg(master->dev, "recv: hdr cmd=0x%x\n", reg);
+ }
+
+ /* Clear Pending Intr */
+ writel(0, master->regs + SVC_I3C_CTRL);
+ dev_dbg(master->dev, "slave tx count %u\n", cmd->len);
+ complete(&xfer->comp);
+ }
+quit:
+ if (master->slave.pending_rd) {
+ master->slave.cur = master->slave.pending_rd;
+ svc_i3c_slave_start_dma(master, master->slave.cur);
+ } else {
+ master->slave.cur = NULL;
+ svc_i3c_slave_enable_interrupts(master, false);
+ }
+}
+
+static irqreturn_t svc_i3c_slave_irq_handler(int irq, void *dev_id)
+{
+ struct svc_i3c_master *master = (struct svc_i3c_master *)dev_id;
+ u32 active = readl(master->regs + SVC_I3C_INTMASKED);
+ u32 status = readl(master->regs + SVC_I3C_STATUS);
+
+ if ((active & SVC_I3C_INT_STOP) && (status & SVC_I3C_INT_STOP)) {
+ writel(SVC_I3C_STATUS_STOP, master->regs + SVC_I3C_STATUS);
+ if (master->slave.cur)
+ svc_i3c_slave_check_complete(master);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int svc_i3c_slave_write(struct i3c_master_controller *m,
+ struct svc_i3c_xfer *xfer)
+{
+ struct svc_i3c_master *master = to_svc_i3c_master(m);
+ int ret;
+ unsigned long flags;
+
+ spin_lock_irqsave(&master->slave.lock, flags);
+ if (master->slave.cur)
+ svc_i3c_slave_stop_dma(master);
+ writel(SVC_I3C_DATACTRL_FLUSHTB, master->regs + SVC_I3C_DATACTRL);
+ master->slave.cur = xfer;
+
+ init_completion(&xfer->comp);
+ svc_i3c_slave_start_dma(master, xfer);
+ svc_i3c_slave_enable_interrupts(master, true);
+
+ /*
+ * Set Pending Intr in GetStatus response to inform that
+ * slave has data to send.
+ */
+ writel(SVC_I3C_CTRL_PENDINT(1), master->regs + SVC_I3C_CTRL);
+ spin_unlock_irqrestore(&master->slave.lock, flags);
+
+ ret = wait_for_completion_timeout(&xfer->comp,
+ msecs_to_jiffies(3000));
+ if (!ret) {
+ /* Clear Pending Intr */
+ writel(0, master->regs + SVC_I3C_CTRL);
+
+ spin_lock_irqsave(&master->slave.lock, flags);
+ svc_i3c_slave_stop_dma(master);
+ if (master->slave.pending_rd) {
+ master->slave.cur = master->slave.pending_rd;
+ svc_i3c_slave_start_dma(master, master->slave.cur);
+ } else {
+ master->slave.cur = NULL;
+ svc_i3c_slave_enable_interrupts(master, false);
+ }
+ spin_unlock_irqrestore(&master->slave.lock, flags);
+ dev_info(master->dev, "slave write timeout\n");
+ xfer->ret = -ETIMEDOUT;
+ return -ETIMEDOUT;
+ }
+
+ xfer->ret = 0;
+ return 0;
+}
+
+static int svc_i3c_slave_priv_xfers(struct i3c_dev_desc *dev,
+ struct i3c_priv_xfer *xfers,
+ int nxfers)
+{
+ struct i3c_master_controller *m = i3c_dev_get_master(dev);
+ struct svc_i3c_master *master = to_svc_i3c_master(m);
+ struct svc_i3c_xfer *xfer;
+ struct svc_i3c_cmd *cmd;
+ int ret;
+
+ /* Only support one write transfer */
+ if (nxfers != 1 || xfers[0].rnw)
+ return -EOPNOTSUPP;
+
+ if (master->slave.cur && master->slave.cur != master->slave.pending_rd)
+ return -EBUSY;
+
+ xfer = svc_i3c_master_alloc_xfer(master, nxfers);
+ if (!xfer)
+ return -ENOMEM;
+
+ cmd = &xfer->cmds[0];
+ cmd->rnw = false;
+ cmd->out = xfers[0].data.out;
+ cmd->len = xfers[0].len;
+ svc_i3c_slave_write(m, xfer);
+
+ ret = xfer->ret;
+ svc_i3c_master_free_xfer(xfer);
+
+ return ret;
+}
+
+static int svc_i3c_slave_bus_init(struct i3c_master_controller *m)
+{
+ struct svc_i3c_master *master = to_svc_i3c_master(m);
+ struct i3c_dev_desc *desc = m->this;
+ u32 partno = (u32)desc->info.pid;
+ struct svc_i3c_xfer *xfer;
+ struct svc_i3c_cmd *cmd;
u32 reg;
- /* Clear pending warnings */
- writel(readl(master->regs + SVC_I3C_MERRWARN),
- master->regs + SVC_I3C_MERRWARN);
+ if (!master->use_dma)
+ return -ENOTSUPP;
- /* Set RX and TX tigger levels, flush FIFOs */
- reg = SVC_I3C_MDATACTRL_FLUSHTB |
- SVC_I3C_MDATACTRL_FLUSHRB |
- SVC_I3C_MDATACTRL_UNLOCK_TRIG |
- SVC_I3C_MDATACTRL_TXTRIG_FIFO_NOT_FULL |
- SVC_I3C_MDATACTRL_RXTRIG_FIFO_NOT_EMPTY;
- writel(reg, master->regs + SVC_I3C_MDATACTRL);
+ /* Set dcr/partno */
+ writel(SVC_I3C_IDEXT_DCR(desc->info.dcr), master->regs + SVC_I3C_IDEXT);
+ writel(partno, master->regs + SVC_I3C_PARTNO);
- svc_i3c_master_disable_interrupts(master);
+ /* Set max rd/wr length */
+ reg = SVC_I3C_MAXLIMITS_MAXRD(MAX_DMA_COUNT) |
+ SVC_I3C_MAXLIMITS_MAXWR(MAX_DMA_COUNT);
+ writel(reg, master->regs + SVC_I3C_MAXLIMITS);
+
+ /* Enable slave mode */
+ reg = readl(master->regs + SVC_I3C_CONFIG);
+ reg |= SVC_I3C_CONFIG_SLVEN;
+ if (master->hdr_ddr)
+ reg |= SVC_I3C_CONFIG_DDROK;
+ writel(reg, master->regs + SVC_I3C_CONFIG);
+
+ /* Prepare one RX transfer */
+ xfer = svc_i3c_master_alloc_xfer(master, 1);
+ if (!xfer)
+ return -ENOMEM;
+
+ cmd = &xfer->cmds[0];
+ cmd->rnw = true;
+ cmd->len = MAX_DMA_COUNT;
+ cmd->in = kzalloc(MAX_DMA_COUNT, GFP_KERNEL);
+ if (!cmd->in) {
+ svc_i3c_master_free_xfer(xfer);
+ return -ENOMEM;
+ }
+ master->slave.pending_rd = xfer;
+ master->slave.cur = xfer;
+ svc_i3c_slave_start_dma(master, xfer);
+ svc_i3c_slave_enable_interrupts(master, true);
+
+ return 0;
+}
+
+static void svc_i3c_slave_bus_cleanup(struct i3c_master_controller *m)
+{
+ struct svc_i3c_master *master = to_svc_i3c_master(m);
+ u32 reg;
+
+ svc_i3c_slave_enable_interrupts(master, false);
+
+ reg = readl(master->regs + SVC_I3C_CONFIG);
+ reg &= ~SVC_I3C_CONFIG_SLVEN;
+ writel(reg, master->regs + SVC_I3C_CONFIG);
+}
+
+static const struct i3c_target_ops svc_i3c_slave_ops = {
+ .bus_init = svc_i3c_slave_bus_init,
+ .bus_cleanup = svc_i3c_slave_bus_cleanup,
+ .priv_xfers = svc_i3c_slave_priv_xfers,
+};
+
+static struct dentry *svc_i3c_debugfs_dir;
+static int debug_show(struct seq_file *seq, void *v)
+{
+ struct svc_i3c_master *master = seq->private;
+
+ seq_printf(seq, "MSTATUS=0x%x\n", readl(master->regs + SVC_I3C_MSTATUS));
+ seq_printf(seq, "MERRWARN=0x%x\n", readl(master->regs + SVC_I3C_MERRWARN));
+ seq_printf(seq, "MCTRL=0x%x\n", readl(master->regs + SVC_I3C_MCTRL));
+ seq_printf(seq, "MDATACTRL=0x%x\n", readl(master->regs + SVC_I3C_MDATACTRL));
+ seq_printf(seq, "MCONFIG=0x%x\n", readl(master->regs + SVC_I3C_MCONFIG));
+
+ return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(debug);
+
+static void svc_i3c_init_debugfs(struct platform_device *pdev,
+ struct svc_i3c_master *master)
+{
+ if (!svc_i3c_debugfs_dir) {
+ svc_i3c_debugfs_dir = debugfs_create_dir("svc_i3c", NULL);
+ if (!svc_i3c_debugfs_dir)
+ return;
+ }
+
+ master->debugfs = debugfs_create_dir(dev_name(&pdev->dev),
+ svc_i3c_debugfs_dir);
+ if (!master->debugfs)
+ return;
+
+ debugfs_create_file("debug", 0444, master->debugfs, master, &debug_fops);
+ debugfs_create_u64("err_cnt", 0444, master->debugfs, &master->err_cnt);
+ debugfs_create_u8("err_code", 0444, master->debugfs, &master->err_code);
+ debugfs_create_u64("rd_ibiwon_cnt", 0444, master->debugfs, &master->rd_ibiwon_cnt);
+ debugfs_create_u64("wr_ibiwon_cnt", 0444, master->debugfs, &master->wr_ibiwon_cnt);
+}
+
+static int svc_i3c_setup_dma(struct platform_device *pdev, struct svc_i3c_master *master)
+{
+ struct device *dev = &pdev->dev;
+ u32 dma_conn, reg_base;
+ int ret;
+
+ if (!of_property_read_bool(dev->of_node, "use-dma"))
+ return 0;
+
+ ret = of_property_read_u32(dev->of_node, "dma-mux", &dma_conn);
+ if (ret) {
+ dev_dbg(dev, "no DMA channel mux configured\n");
+ return 0;
+ }
+
+ master->dma_regs = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(master->dma_regs))
+ return 0;
+
+ master->dma_mux_regs = devm_platform_ioremap_resource(pdev, 2);
+ if (IS_ERR(master->dma_mux_regs))
+ return 0;
+
+ /* DMA TX transfer width is 32 bits(MWDATAB width) for each byte sent to I3C bus */
+ master->dma_tx_buf = dma_alloc_coherent(dev, MAX_DMA_COUNT * 4,
+ &master->dma_tx_addr, GFP_KERNEL);
+ if (!master->dma_tx_buf)
+ return -ENOMEM;
+
+ master->dma_rx_buf = dma_alloc_coherent(dev, MAX_DMA_COUNT,
+ &master->dma_rx_addr, GFP_KERNEL);
+ if (!master->dma_rx_buf) {
+ dma_free_coherent(master->dev, MAX_DMA_COUNT * 4, master->dma_tx_buf,
+ master->dma_tx_addr);
+ return -ENOMEM;
+ }
+
+ /*
+ * Set DMA channel connectivity
+ * channel 0: I3C TX, channel 1: I3C RX
+ */
+ writel(0x00600060 | (dma_conn + 1) << 16 | dma_conn, master->dma_mux_regs);
+ master->use_dma = true;
+ dev_info(dev, "Using DMA (mux %d)\n", dma_conn);
+
+ of_property_read_u32_index(dev->of_node, "reg", 0, &reg_base);
+ /*
+ * Setup GDMA Channel for TX (Memory to I3C FIFO)
+ */
+ writel(master->dma_tx_addr, master->dma_regs + NPCM_GDMA_SRCB(DMA_CH_TX));
+ writel(reg_base + SVC_I3C_MWDATAB, master->dma_regs +
+ NPCM_GDMA_DSTB(DMA_CH_TX));
+ /*
+ * Setup GDMA Channel for RX (I3C FIFO to Memory)
+ */
+ writel(reg_base + SVC_I3C_MRDATAB, master->dma_regs +
+ NPCM_GDMA_SRCB(DMA_CH_RX));
+ writel(master->dma_rx_addr, master->dma_regs + NPCM_GDMA_DSTB(DMA_CH_RX));
+
+ return 0;
}
static int svc_i3c_master_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct svc_i3c_master *master;
+ struct reset_control *reset;
+ const char *role;
+ u32 val;
int ret;
master = devm_kzalloc(dev, sizeof(*master), GFP_KERNEL);
@@ -1437,67 +2669,108 @@ static int svc_i3c_master_probe(struct platform_device *pdev)
return PTR_ERR(master->sclk);
master->irq = platform_get_irq(pdev, 0);
- if (master->irq <= 0)
- return -ENOENT;
+ if (master->irq < 0)
+ return master->irq;
master->dev = dev;
- svc_i3c_master_reset(master);
-
- ret = clk_prepare_enable(master->pclk);
+ ret = svc_i3c_master_prepare_clks(master);
if (ret)
return ret;
- ret = clk_prepare_enable(master->fclk);
- if (ret)
- goto err_disable_pclk;
-
- ret = clk_prepare_enable(master->sclk);
- if (ret)
- goto err_disable_fclk;
-
+ reset = devm_reset_control_get(&pdev->dev, NULL);
+ if (!IS_ERR(reset)) {
+ reset_control_assert(reset);
+ udelay(5);
+ reset_control_deassert(reset);
+ }
INIT_WORK(&master->hj_work, svc_i3c_master_hj_work);
- INIT_WORK(&master->ibi_work, svc_i3c_master_ibi_work);
- mutex_init(&master->lock);
-
- ret = devm_request_irq(dev, master->irq, svc_i3c_master_irq_handler,
- IRQF_NO_SUSPEND, "svc-i3c-irq", master);
+ ret = of_property_read_string(pdev->dev.of_node, "initial-role", &role);
+ if (!ret && !strcmp("target", role))
+ ret = devm_request_irq(dev, master->irq, svc_i3c_slave_irq_handler,
+ IRQF_NO_SUSPEND, "svc-i3c-irq", master);
+ else
+ ret = devm_request_irq(dev, master->irq, svc_i3c_master_irq_handler,
+ IRQF_NO_SUSPEND, "svc-i3c-irq", master);
if (ret)
- goto err_disable_sclk;
+ goto err_disable_clks;
master->free_slots = GENMASK(SVC_I3C_MAX_DEVS - 1, 0);
- spin_lock_init(&master->xferqueue.lock);
+ mutex_init(&master->lock);
INIT_LIST_HEAD(&master->xferqueue.list);
+ spin_lock_init(&master->req_lock);
spin_lock_init(&master->ibi.lock);
+ spin_lock_init(&master->slave.lock);
master->ibi.num_slots = SVC_I3C_MAX_DEVS;
master->ibi.slots = devm_kcalloc(&pdev->dev, master->ibi.num_slots,
sizeof(*master->ibi.slots),
GFP_KERNEL);
if (!master->ibi.slots) {
ret = -ENOMEM;
- goto err_disable_sclk;
+ goto err_disable_clks;
}
platform_set_drvdata(pdev, master);
+ pm_runtime_set_autosuspend_delay(&pdev->dev, SVC_I3C_PM_TIMEOUT_MS);
+ pm_runtime_use_autosuspend(&pdev->dev);
+ pm_runtime_get_noresume(&pdev->dev);
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+
+ svc_i3c_master_reset(master);
+
+ if (of_property_read_bool(dev->of_node, "hdr-ddr")) {
+ dev_info(master->dev, "support hdr-ddr\n");
+ master->hdr_ddr = true;
+ }
+ if (of_property_read_bool(dev->of_node, "enable-hj"))
+ master->en_hj = true;
+ if (!of_property_read_u32(dev->of_node, "i3c-pp-scl-hi-period-ns", &val))
+ master->scl_timing.i3c_pp_hi = val;
+
+ if (!of_property_read_u32(dev->of_node, "i3c-pp-scl-lo-period-ns", &val))
+ master->scl_timing.i3c_pp_lo = val;
+
+ if (!of_property_read_u32(dev->of_node, "i3c-od-scl-hi-period-ns", &val))
+ master->scl_timing.i3c_od_hi = val;
+
+ if (!of_property_read_u32(dev->of_node, "i3c-od-scl-lo-period-ns", &val))
+ master->scl_timing.i3c_od_lo = val;
+
+ svc_i3c_master_clear_merrwarn(master);
+ svc_i3c_master_flush_fifo(master);
+
+ svc_i3c_setup_dma(pdev, master);
+ svc_i3c_init_debugfs(pdev, master);
+
/* Register the master */
- ret = i3c_master_register(&master->base, &pdev->dev,
- &svc_i3c_master_ops, false);
+ ret = i3c_register(&master->base, &pdev->dev,
+ &svc_i3c_master_ops, &svc_i3c_slave_ops, false);
if (ret)
- goto err_disable_sclk;
+ goto rpm_disable;
+ pm_runtime_mark_last_busy(&pdev->dev);
+ pm_runtime_put_autosuspend(&pdev->dev);
+
+ if (master->en_hj) {
+ dev_info(master->dev, "enable hot-join\n");
+ svc_i3c_master_enable_interrupts(master, SVC_I3C_MINT_SLVSTART);
+ }
+ master->probe_done = true;
return 0;
-err_disable_sclk:
- clk_disable_unprepare(master->sclk);
+rpm_disable:
+ pm_runtime_dont_use_autosuspend(&pdev->dev);
+ pm_runtime_put_noidle(&pdev->dev);
+ pm_runtime_set_suspended(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+ debugfs_remove_recursive(master->debugfs);
-err_disable_fclk:
- clk_disable_unprepare(master->fclk);
-
-err_disable_pclk:
- clk_disable_unprepare(master->pclk);
+err_disable_clks:
+ svc_i3c_master_unprepare_clks(master);
return ret;
}
@@ -1507,21 +2780,79 @@ static int svc_i3c_master_remove(struct platform_device *pdev)
struct svc_i3c_master *master = platform_get_drvdata(pdev);
int ret;
- ret = i3c_master_unregister(&master->base);
+ /* Avoid ibi events during driver unbinding */
+ writel(SVC_I3C_MINT_SLVSTART, master->regs + SVC_I3C_MINTCLR);
+
+ debugfs_remove_recursive(master->debugfs);
+
+ ret = i3c_unregister(&master->base);
if (ret)
return ret;
- clk_disable_unprepare(master->pclk);
- clk_disable_unprepare(master->fclk);
- clk_disable_unprepare(master->sclk);
+ pm_runtime_dont_use_autosuspend(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+ if (master->use_dma) {
+ dma_free_coherent(master->dev, MAX_DMA_COUNT * 4, master->dma_tx_buf,
+ master->dma_tx_addr);
+ dma_free_coherent(master->dev, MAX_DMA_COUNT, master->dma_rx_buf,
+ master->dma_rx_addr);
+ }
return 0;
}
+static void svc_i3c_save_regs(struct svc_i3c_master *master)
+{
+ master->saved_regs.mconfig = readl(master->regs + SVC_I3C_MCONFIG);
+ master->saved_regs.mdynaddr = readl(master->regs + SVC_I3C_MDYNADDR);
+}
+
+static void svc_i3c_restore_regs(struct svc_i3c_master *master)
+{
+ if (readl(master->regs + SVC_I3C_MDYNADDR) !=
+ master->saved_regs.mdynaddr) {
+ writel(master->saved_regs.mconfig,
+ master->regs + SVC_I3C_MCONFIG);
+ writel(master->saved_regs.mdynaddr,
+ master->regs + SVC_I3C_MDYNADDR);
+ }
+}
+
+static int __maybe_unused svc_i3c_runtime_suspend(struct device *dev)
+{
+ struct svc_i3c_master *master = dev_get_drvdata(dev);
+
+ svc_i3c_save_regs(master);
+ svc_i3c_master_unprepare_clks(master);
+ pinctrl_pm_select_sleep_state(dev);
+
+ return 0;
+}
+
+static int __maybe_unused svc_i3c_runtime_resume(struct device *dev)
+{
+ struct svc_i3c_master *master = dev_get_drvdata(dev);
+
+ pinctrl_pm_select_default_state(dev);
+ svc_i3c_master_prepare_clks(master);
+
+ svc_i3c_restore_regs(master);
+
+ return 0;
+}
+
+static const struct dev_pm_ops svc_i3c_pm_ops = {
+ SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+ SET_RUNTIME_PM_OPS(svc_i3c_runtime_suspend,
+ svc_i3c_runtime_resume, NULL)
+};
+
static const struct of_device_id svc_i3c_master_of_match_tbl[] = {
{ .compatible = "silvaco,i3c-master" },
{ /* sentinel */ },
};
+MODULE_DEVICE_TABLE(of, svc_i3c_master_of_match_tbl);
static struct platform_driver svc_i3c_master = {
.probe = svc_i3c_master_probe,
@@ -1529,6 +2860,7 @@ static struct platform_driver svc_i3c_master = {
.driver = {
.name = "silvaco-i3c-master",
.of_match_table = svc_i3c_master_of_match_tbl,
+ .pm = &svc_i3c_pm_ops,
},
};
module_platform_driver(svc_i3c_master);
diff --git a/drivers/i3c/mctp/Kconfig b/drivers/i3c/mctp/Kconfig
new file mode 100644
index 000000000000..fe635940138b
--- /dev/null
+++ b/drivers/i3c/mctp/Kconfig
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config I3C_MCTP
+ tristate "I3C Controller MCTP driver"
+ depends on I3C
+help
+ Say yes here to enable the I3C MCTP driver for I3C HW that is
+ configured as an I3C Controller Device on the I3C Bus.
+
+config I3C_TARGET_MCTP
+ tristate "I3C Target MCTP driver"
+ depends on I3C
+help
+ Say yes here to enable the I3C MCTP driver for I3C HW that is
+ configured as an I3C Target Device on the I3C Bus.
diff --git a/drivers/i3c/mctp/Makefile b/drivers/i3c/mctp/Makefile
new file mode 100644
index 000000000000..05eb78684843
--- /dev/null
+++ b/drivers/i3c/mctp/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_I3C_MCTP) += i3c-mctp.o
+obj-$(CONFIG_I3C_TARGET_MCTP) += i3c-target-mctp.o
diff --git a/drivers/i3c/mctp/i3c-mctp.c b/drivers/i3c/mctp/i3c-mctp.c
new file mode 100644
index 000000000000..46cc210377c0
--- /dev/null
+++ b/drivers/i3c/mctp/i3c-mctp.c
@@ -0,0 +1,624 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2022 Intel Corporation.*/
+
+#include <linux/cdev.h>
+#include <linux/fs.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <linux/preempt.h>
+#include <linux/ptr_ring.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+
+#include <linux/i3c/device.h>
+
+#include <linux/i3c/mctp/i3c-mctp.h>
+
+#define I3C_MCTP_MINORS 32
+#define CCC_DEVICE_STATUS_PENDING_INTR(x) (((x) & GENMASK(3, 0)) >> 0)
+#define POLLING_TIMEOUT_MS 50
+#define MCTP_INTERRUPT_NUMBER 1
+#define RX_RING_COUNT 16
+#define I3C_MCTP_MIN_TRANSFER_SIZE 69
+#define I3C_MCTP_IBI_PAYLOAD_SIZE 2
+#define POLL_MODE BIT(0)
+
+static const struct i3c_device_id i3c_mctp_ids[] = {
+ I3C_DEVICE(0x319, 0x8000, (void *)POLL_MODE),
+ I3C_CLASS(0xCC, 0),
+ { },
+};
+
+struct i3c_mctp {
+ struct i3c_device *i3c;
+ struct cdev cdev;
+ struct device *dev;
+ struct delayed_work polling_work;
+ struct platform_device *i3c_peci;
+ int id;
+ /*
+ * Restrict an access to the /dev descriptor to one
+ * user at a time.
+ */
+ spinlock_t device_file_lock;
+ int device_open;
+ /* Currently only one userspace client is supported */
+ struct i3c_mctp_client *default_client;
+ struct i3c_mctp_client *peci_client;
+ u16 max_read_len;
+ u16 max_write_len;
+ bool poll_mode;
+};
+
+struct i3c_mctp_client {
+ struct i3c_mctp *priv;
+ struct ptr_ring rx_queue;
+ wait_queue_head_t wait_queue;
+};
+
+static struct class *i3c_mctp_class;
+static dev_t i3c_mctp_devt;
+static DEFINE_IDA(i3c_mctp_ida);
+
+static struct kmem_cache *packet_cache;
+
+/**
+ * i3c_mctp_packet_alloc() - allocates i3c_mctp_packet
+ *
+ * @flags: the type of memory to allocate
+ *
+ * Allocates i3c_mctp_packet via slab allocation
+ * Return: pointer to the packet, NULL if some error occurred
+ */
+void *i3c_mctp_packet_alloc(gfp_t flags)
+{
+ return kmem_cache_alloc(packet_cache, flags);
+}
+EXPORT_SYMBOL_GPL(i3c_mctp_packet_alloc);
+
+/**
+ * i3c_mctp_packet_free() - frees i3c_mctp_packet
+ *
+ * @packet: pointer to the packet which should be freed
+ *
+ * Frees i3c_mctp_packet previously allocated via slab allocation
+ */
+void i3c_mctp_packet_free(void *packet)
+{
+ kmem_cache_free(packet_cache, packet);
+}
+EXPORT_SYMBOL_GPL(i3c_mctp_packet_free);
+
+static void i3c_mctp_client_free(struct i3c_mctp_client *client)
+{
+ ptr_ring_cleanup(&client->rx_queue, &i3c_mctp_packet_free);
+
+ kfree(client);
+}
+
+static struct i3c_mctp_client *i3c_mctp_client_alloc(struct i3c_mctp *priv)
+{
+ struct i3c_mctp_client *client;
+ int ret;
+
+ client = kzalloc(sizeof(*client), GFP_KERNEL);
+ if (!client)
+ goto out;
+
+ client->priv = priv;
+ ret = ptr_ring_init(&client->rx_queue, RX_RING_COUNT, GFP_KERNEL);
+ if (ret)
+ return ERR_PTR(ret);
+ init_waitqueue_head(&client->wait_queue);
+out:
+ return client;
+}
+
+static struct i3c_mctp_client *i3c_mctp_find_client(struct i3c_mctp *priv,
+ struct i3c_mctp_packet *packet)
+{
+ u8 *msg_hdr = (u8 *)packet->data.payload;
+ u8 mctp_type = msg_hdr[MCTP_MSG_HDR_MSG_TYPE_OFFSET];
+ u16 vendor = (msg_hdr[MCTP_MSG_HDR_VENDOR_OFFSET] << 8
+ | msg_hdr[MCTP_MSG_HDR_VENDOR_OFFSET + 1]);
+ u8 intel_msg_op_code = msg_hdr[MCTP_MSG_HDR_OPCODE_OFFSET];
+
+ if (priv->peci_client && mctp_type == MCTP_MSG_TYPE_VDM_PCI &&
+ vendor == MCTP_VDM_PCI_INTEL_VENDOR_ID && intel_msg_op_code == MCTP_VDM_PCI_INTEL_PECI)
+ return priv->peci_client;
+
+ return priv->default_client;
+}
+
+static struct i3c_mctp_packet *i3c_mctp_read_packet(struct i3c_device *i3c)
+{
+ struct i3c_mctp *priv = dev_get_drvdata(i3cdev_to_dev(i3c));
+ struct i3c_mctp_packet *rx_packet;
+ struct i3c_priv_xfer xfers = {
+ .rnw = true,
+ };
+ int ret;
+
+ rx_packet = i3c_mctp_packet_alloc(GFP_KERNEL);
+ if (!rx_packet)
+ return ERR_PTR(-ENOMEM);
+
+ rx_packet->size = I3C_MCTP_PACKET_SIZE;
+ xfers.len = rx_packet->size;
+ xfers.data.in = &rx_packet->data;
+
+ /* Check against packet size + PEC byte to make sure that we always try to read max */
+ if (priv->max_read_len < xfers.len) {
+ dev_dbg(i3cdev_to_dev(i3c), "Length mismatch. MRL = %d, xfers.len = %d",
+ priv->max_read_len, xfers.len);
+ i3c_mctp_packet_free(rx_packet);
+ return ERR_PTR(-EINVAL);
+ }
+
+ ret = i3c_device_do_priv_xfers(i3c, &xfers, 1);
+ if (ret) {
+ i3c_mctp_packet_free(rx_packet);
+ return ERR_PTR(ret);
+ }
+ rx_packet->size = xfers.len;
+
+ return rx_packet;
+}
+
+static void i3c_mctp_dispatch_packet(struct i3c_mctp *priv, struct i3c_mctp_packet *packet)
+{
+ struct i3c_mctp_client *client = i3c_mctp_find_client(priv, packet);
+ int ret;
+
+ ret = ptr_ring_produce(&client->rx_queue, packet);
+ if (ret)
+ i3c_mctp_packet_free(packet);
+ else
+ wake_up_all(&client->wait_queue);
+}
+
+static void i3c_mctp_polling_work(struct work_struct *work)
+{
+ struct i3c_mctp *priv = container_of(to_delayed_work(work), struct i3c_mctp, polling_work);
+ struct i3c_device *i3cdev = priv->i3c;
+ struct i3c_mctp_packet *rx_packet;
+ struct i3c_device_info info;
+ int ret;
+
+ i3c_device_get_info(i3cdev, &info);
+ ret = i3c_device_getstatus_ccc(i3cdev, &info);
+ if (ret)
+ goto out;
+
+ if (CCC_DEVICE_STATUS_PENDING_INTR(info.status) != MCTP_INTERRUPT_NUMBER)
+ goto out;
+
+ rx_packet = i3c_mctp_read_packet(i3cdev);
+ if (IS_ERR(rx_packet))
+ goto out;
+
+ i3c_mctp_dispatch_packet(priv, rx_packet);
+out:
+ schedule_delayed_work(&priv->polling_work, msecs_to_jiffies(POLLING_TIMEOUT_MS));
+}
+
+static ssize_t i3c_mctp_write(struct file *file, const char __user *buf, size_t count,
+ loff_t *f_pos)
+{
+ struct i3c_mctp *priv = file->private_data;
+ struct i3c_device *i3c = priv->i3c;
+ struct i3c_priv_xfer xfers = {
+ .rnw = false,
+ .len = count,
+ };
+ u8 *data;
+ int ret;
+
+ /*
+ * Check against packet size + PEC byte
+ * to not send more data than it was set in the probe
+ */
+ if (priv->max_write_len < xfers.len + 1) {
+ dev_dbg(i3cdev_to_dev(i3c), "Length mismatch. MWL = %d, xfers.len = %d",
+ priv->max_write_len, xfers.len);
+ return -EINVAL;
+ }
+
+ data = memdup_user(buf, count);
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ xfers.data.out = data;
+
+ ret = i3c_device_do_priv_xfers(i3c, &xfers, 1);
+ kfree(data);
+ return ret ?: count;
+}
+
+static ssize_t i3c_mctp_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos)
+{
+ struct i3c_mctp *priv = file->private_data;
+ struct i3c_mctp_client *client = priv->default_client;
+ struct i3c_mctp_packet *rx_packet;
+
+ rx_packet = ptr_ring_consume(&client->rx_queue);
+ if (!rx_packet)
+ return 0;
+
+ if (count > rx_packet->size)
+ count = rx_packet->size;
+
+ if (copy_to_user(buf, &rx_packet->data, count))
+ return -EFAULT;
+
+ i3c_mctp_packet_free(rx_packet);
+
+ return count;
+}
+
+static int i3c_mctp_open(struct inode *inode, struct file *file)
+{
+ struct i3c_mctp *priv = container_of(inode->i_cdev, struct i3c_mctp, cdev);
+
+ spin_lock(&priv->device_file_lock);
+ if (priv->device_open) {
+ spin_unlock(&priv->device_file_lock);
+ return -EBUSY;
+ }
+ priv->device_open++;
+ spin_unlock(&priv->device_file_lock);
+
+ file->private_data = priv;
+
+ return 0;
+}
+
+static int i3c_mctp_release(struct inode *inode, struct file *file)
+{
+ struct i3c_mctp *priv = file->private_data;
+
+ spin_lock(&priv->device_file_lock);
+ priv->device_open--;
+ spin_unlock(&priv->device_file_lock);
+
+ file->private_data = NULL;
+
+ return 0;
+}
+
+static __poll_t i3c_mctp_poll(struct file *file, struct poll_table_struct *pt)
+{
+ struct i3c_mctp *priv = file->private_data;
+ __poll_t ret = 0;
+
+ poll_wait(file, &priv->default_client->wait_queue, pt);
+
+ if (__ptr_ring_peek(&priv->default_client->rx_queue))
+ ret |= EPOLLIN;
+
+ return ret;
+}
+
+static const struct file_operations i3c_mctp_fops = {
+ .owner = THIS_MODULE,
+ .read = i3c_mctp_read,
+ .write = i3c_mctp_write,
+ .poll = i3c_mctp_poll,
+ .open = i3c_mctp_open,
+ .release = i3c_mctp_release,
+};
+
+/**
+ * i3c_mctp_add_peci_client() - registers PECI client
+ * @i3c: I3C device to get the PECI client for
+ *
+ * Return: pointer to PECI client, -ENOMEM - in case of client alloc fault
+ */
+struct i3c_mctp_client *i3c_mctp_add_peci_client(struct i3c_device *i3c)
+{
+ struct i3c_mctp *priv = dev_get_drvdata(i3cdev_to_dev(i3c));
+ struct i3c_mctp_client *client;
+
+ client = i3c_mctp_client_alloc(priv);
+ if (IS_ERR(client))
+ return ERR_PTR(-ENOMEM);
+
+ priv->peci_client = client;
+
+ return priv->peci_client;
+}
+EXPORT_SYMBOL_GPL(i3c_mctp_add_peci_client);
+
+/**
+ * i3c_mctp_remove_peci_client() - un-registers PECI client
+ * @client: i3c_mctp_client to be freed
+ */
+void i3c_mctp_remove_peci_client(struct i3c_mctp_client *client)
+{
+ struct i3c_mctp *priv = client->priv;
+
+ i3c_mctp_client_free(priv->peci_client);
+
+ priv->peci_client = NULL;
+}
+EXPORT_SYMBOL_GPL(i3c_mctp_remove_peci_client);
+
+static struct i3c_mctp *i3c_mctp_alloc(struct i3c_device *i3c)
+{
+ struct i3c_mctp *priv;
+ int id;
+
+ priv = devm_kzalloc(i3cdev_to_dev(i3c), sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return ERR_PTR(-ENOMEM);
+
+ id = ida_alloc(&i3c_mctp_ida, GFP_KERNEL);
+ if (id < 0) {
+ pr_err("i3c_mctp: no minor number available!\n");
+ return ERR_PTR(id);
+ }
+
+ priv->id = id;
+ priv->i3c = i3c;
+
+ spin_lock_init(&priv->device_file_lock);
+
+ return priv;
+}
+
+static void i3c_mctp_ibi_handler(struct i3c_device *dev, const struct i3c_ibi_payload *payload)
+{
+ struct i3c_mctp *priv = dev_get_drvdata(i3cdev_to_dev(dev));
+ struct i3c_mctp_packet *rx_packet;
+
+ rx_packet = i3c_mctp_read_packet(dev);
+ if (IS_ERR(rx_packet))
+ return;
+
+ i3c_mctp_dispatch_packet(priv, rx_packet);
+}
+
+static int i3c_mctp_init(struct i3c_driver *drv)
+{
+ int ret;
+
+ packet_cache = kmem_cache_create_usercopy("mctp-i3c-packet",
+ sizeof(struct i3c_mctp_packet), 0, 0, 0,
+ sizeof(struct i3c_mctp_packet), NULL);
+ if (IS_ERR(packet_cache)) {
+ ret = PTR_ERR(packet_cache);
+ goto out;
+ }
+
+ /* Dynamically request unused major number */
+ ret = alloc_chrdev_region(&i3c_mctp_devt, 0, I3C_MCTP_MINORS, "i3c-mctp");
+ if (ret)
+ goto out;
+
+ /* Create a class to populate sysfs entries*/
+ i3c_mctp_class = class_create(THIS_MODULE, "i3c-mctp");
+ if (IS_ERR(i3c_mctp_class)) {
+ ret = PTR_ERR(i3c_mctp_class);
+ goto out_unreg_chrdev;
+ }
+
+ i3c_driver_register(drv);
+
+ return 0;
+
+out_unreg_chrdev:
+ unregister_chrdev_region(i3c_mctp_devt, I3C_MCTP_MINORS);
+out:
+ pr_err("i3c_mctp: driver initialisation failed\n");
+ return ret;
+}
+
+static void i3c_mctp_free(struct i3c_driver *drv)
+{
+ i3c_driver_unregister(drv);
+ class_destroy(i3c_mctp_class);
+ unregister_chrdev_region(i3c_mctp_devt, I3C_MCTP_MINORS);
+ kmem_cache_destroy(packet_cache);
+}
+
+static int i3c_mctp_enable_ibi(struct i3c_device *i3cdev)
+{
+ struct i3c_ibi_setup ibireq = {
+ .handler = i3c_mctp_ibi_handler,
+ .max_payload_len = 2,
+ .num_slots = 10,
+ };
+ int ret;
+
+ ret = i3c_device_request_ibi(i3cdev, &ibireq);
+ if (ret)
+ return ret;
+ ret = i3c_device_enable_ibi(i3cdev);
+ if (ret)
+ i3c_device_free_ibi(i3cdev);
+
+ return ret;
+}
+
+/**
+ * i3c_mctp_get_eid() - receive MCTP EID assigned to the device
+ *
+ * @client: client for the device to get the EID for
+ * @domain_id: requested domain ID
+ * @eid: pointer to store EID value
+ *
+ * Receive MCTP endpoint ID dynamically assigned by the MCTP Bus Owner
+ * Return: 0 in case of success, a negative error code otherwise.
+ */
+int i3c_mctp_get_eid(struct i3c_mctp_client *client, u8 domain_id, u8 *eid)
+{
+ /* TODO: Implement EID assignment basing on domain ID */
+ *eid = 1;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(i3c_mctp_get_eid);
+
+/**
+ * i3c_mctp_send_packet() - send mctp packet
+ *
+ * @tx_packet: the allocated packet that needs to be send via I3C
+ * @i3c: i3c device to send the packet to
+ *
+ * Return: 0 in case of success, a negative error code otherwise.
+ */
+int i3c_mctp_send_packet(struct i3c_device *i3c, struct i3c_mctp_packet *tx_packet)
+{
+ struct i3c_priv_xfer xfers = {
+ .rnw = false,
+ .len = tx_packet->size,
+ .data.out = &tx_packet->data,
+ };
+
+ return i3c_device_do_priv_xfers(i3c, &xfers, 1);
+}
+EXPORT_SYMBOL_GPL(i3c_mctp_send_packet);
+
+/**
+ * i3c_mctp_receive_packet() - receive mctp packet
+ *
+ * @client: i3c_mctp_client to receive the packet from
+ * @timeout: timeout, in jiffies
+ *
+ * The function will sleep for up to @timeout if no packet is ready to read.
+ *
+ * Returns struct i3c_mctp_packet from or ERR_PTR in case of error or the
+ * timeout elapsed.
+ */
+struct i3c_mctp_packet *i3c_mctp_receive_packet(struct i3c_mctp_client *client,
+ unsigned long timeout)
+{
+ struct i3c_mctp_packet *rx_packet;
+ int ret;
+
+ ret = wait_event_interruptible_timeout(client->wait_queue,
+ __ptr_ring_peek(&client->rx_queue), timeout);
+ if (ret < 0)
+ return ERR_PTR(ret);
+ else if (ret == 0)
+ return ERR_PTR(-ETIME);
+
+ rx_packet = ptr_ring_consume(&client->rx_queue);
+ if (!rx_packet)
+ return ERR_PTR(-EAGAIN);
+
+ return rx_packet;
+}
+EXPORT_SYMBOL_GPL(i3c_mctp_receive_packet);
+
+static int i3c_mctp_probe(struct i3c_device *i3cdev)
+{
+ int ibi_payload_size = I3C_MCTP_IBI_PAYLOAD_SIZE;
+ struct device *dev = i3cdev_to_dev(i3cdev);
+ const struct i3c_device_id *id = i3c_device_match_id(i3cdev, i3c_mctp_ids);
+ struct i3c_device_info info;
+ struct i3c_mctp *priv;
+ int ret;
+
+ priv = i3c_mctp_alloc(i3cdev);
+ if (IS_ERR(priv))
+ return PTR_ERR(priv);
+
+ cdev_init(&priv->cdev, &i3c_mctp_fops);
+
+ priv->cdev.owner = THIS_MODULE;
+ ret = cdev_add(&priv->cdev, MKDEV(MAJOR(i3c_mctp_devt), priv->id), 1);
+ if (ret)
+ goto error_cdev;
+
+ /* register this i3c device with the driver core */
+ priv->dev = device_create(i3c_mctp_class, dev,
+ MKDEV(MAJOR(i3c_mctp_devt), priv->id),
+ NULL, "i3c-mctp-%d", priv->id);
+ if (IS_ERR(priv->dev)) {
+ ret = PTR_ERR(priv->dev);
+ goto error;
+ }
+
+ priv->default_client = i3c_mctp_client_alloc(priv);
+ if (IS_ERR(priv->default_client))
+ goto error;
+
+ dev_set_drvdata(i3cdev_to_dev(i3cdev), priv);
+
+ priv->i3c_peci = platform_device_register_data(i3cdev_to_dev(i3cdev), "peci-i3c", priv->id,
+ NULL, 0);
+ if (IS_ERR(priv->i3c_peci))
+ dev_warn(priv->dev, "failed to register peci-i3c device\n");
+
+ if (id->data == (void *)POLL_MODE || i3c_mctp_enable_ibi(i3cdev)) {
+ INIT_DELAYED_WORK(&priv->polling_work, i3c_mctp_polling_work);
+ schedule_delayed_work(&priv->polling_work, msecs_to_jiffies(POLLING_TIMEOUT_MS));
+ ibi_payload_size = 0;
+ priv->poll_mode = true;
+ }
+
+ i3c_device_get_info(i3cdev, &info);
+
+ ret = i3c_device_getmrl_ccc(i3cdev, &info);
+ if (ret || info.max_read_len < I3C_MCTP_MIN_TRANSFER_SIZE)
+ ret = i3c_device_setmrl_ccc(i3cdev, &info, I3C_MCTP_MIN_TRANSFER_SIZE,
+ ibi_payload_size);
+ if (ret && info.max_read_len < I3C_MCTP_MIN_TRANSFER_SIZE) {
+ dev_err(dev, "Failed to set MRL!, ret = %d\n", ret);
+ goto error_peci;
+ }
+ priv->max_read_len = info.max_read_len;
+
+ ret = i3c_device_getmwl_ccc(i3cdev, &info);
+ if (ret || info.max_write_len < I3C_MCTP_MIN_TRANSFER_SIZE)
+ ret = i3c_device_setmwl_ccc(i3cdev, &info, I3C_MCTP_MIN_TRANSFER_SIZE);
+ if (ret && info.max_write_len < I3C_MCTP_MIN_TRANSFER_SIZE) {
+ dev_err(dev, "Failed to set MWL!, ret = %d\n", ret);
+ goto error_peci;
+ }
+ priv->max_write_len = info.max_write_len;
+
+ return 0;
+
+error_peci:
+ platform_device_unregister(priv->i3c_peci);
+ i3c_device_disable_ibi(i3cdev);
+ i3c_device_free_ibi(i3cdev);
+error:
+ cdev_del(&priv->cdev);
+error_cdev:
+ put_device(dev);
+ return ret;
+}
+
+static void i3c_mctp_remove(struct i3c_device *i3cdev)
+{
+ struct i3c_mctp *priv = dev_get_drvdata(i3cdev_to_dev(i3cdev));
+
+ if (priv->poll_mode)
+ cancel_delayed_work_sync(&priv->polling_work);
+ i3c_device_disable_ibi(i3cdev);
+ i3c_device_free_ibi(i3cdev);
+ i3c_mctp_client_free(priv->default_client);
+ priv->default_client = NULL;
+ platform_device_unregister(priv->i3c_peci);
+
+ device_destroy(i3c_mctp_class, MKDEV(MAJOR(i3c_mctp_devt), priv->id));
+ cdev_del(&priv->cdev);
+ ida_free(&i3c_mctp_ida, priv->id);
+}
+
+static struct i3c_driver i3c_mctp_drv = {
+ .driver.name = "i3c-mctp",
+ .id_table = i3c_mctp_ids,
+ .probe = i3c_mctp_probe,
+ .remove = i3c_mctp_remove,
+};
+
+module_driver(i3c_mctp_drv, i3c_mctp_init, i3c_mctp_free);
+MODULE_AUTHOR("Oleksandr Shulzhenko <oleksandr.shulzhenko.viktorovych@intel.com>");
+MODULE_DESCRIPTION("I3C MCTP driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/i3c/mctp/i3c-target-mctp.c b/drivers/i3c/mctp/i3c-target-mctp.c
new file mode 100644
index 000000000000..448b4bf8d376
--- /dev/null
+++ b/drivers/i3c/mctp/i3c-target-mctp.c
@@ -0,0 +1,389 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2022 Intel Corporation.*/
+
+#include <linux/cdev.h>
+#include <linux/idr.h>
+#include <linux/module.h>
+#include <linux/poll.h>
+#include <linux/ptr_ring.h>
+#include <linux/workqueue.h>
+
+#include <linux/i3c/device.h>
+
+#define I3C_TARGET_MCTP_MINORS 32
+#define RX_RING_COUNT 16
+
+static struct class *i3c_target_mctp_class;
+static dev_t i3c_target_mctp_devt;
+static DEFINE_IDA(i3c_target_mctp_ida);
+
+struct mctp_client;
+
+struct i3c_target_mctp {
+ struct i3c_device *i3cdev;
+ struct cdev cdev;
+ int id;
+ struct mctp_client *client;
+ spinlock_t client_lock; /* to protect client access */
+};
+
+struct mctp_client {
+ struct kref ref;
+ struct i3c_target_mctp *priv;
+ struct ptr_ring rx_queue;
+ wait_queue_head_t wait_queue;
+};
+
+struct mctp_packet {
+ u8 *data;
+ u16 count;
+};
+
+static void *i3c_target_mctp_packet_alloc(u16 count)
+{
+ struct mctp_packet *packet;
+ u8 *data;
+
+ packet = kzalloc(sizeof(*packet), GFP_ATOMIC);
+ if (!packet)
+ return NULL;
+
+ data = kzalloc(count, GFP_ATOMIC);
+ if (!data) {
+ kfree(packet);
+ return NULL;
+ }
+
+ packet->data = data;
+ packet->count = count;
+
+ return packet;
+}
+
+static void i3c_target_mctp_packet_free(void *data)
+{
+ struct mctp_packet *packet = data;
+
+ kfree(packet->data);
+ kfree(packet);
+}
+
+static struct mctp_client *i3c_target_mctp_client_alloc(struct i3c_target_mctp *priv)
+{
+ struct mctp_client *client;
+
+ client = kzalloc(sizeof(*client), GFP_KERNEL);
+ if (!client)
+ goto out;
+
+ kref_init(&client->ref);
+ client->priv = priv;
+ ptr_ring_init(&client->rx_queue, RX_RING_COUNT, GFP_KERNEL);
+out:
+ return client;
+}
+
+static void i3c_target_mctp_client_free(struct kref *ref)
+{
+ struct mctp_client *client = container_of(ref, typeof(*client), ref);
+
+ ptr_ring_cleanup(&client->rx_queue, &i3c_target_mctp_packet_free);
+
+ kfree(client);
+}
+
+static void i3c_target_mctp_client_get(struct mctp_client *client)
+{
+ kref_get(&client->ref);
+}
+
+static void i3c_target_mctp_client_put(struct mctp_client *client)
+{
+ kref_put(&client->ref, &i3c_target_mctp_client_free);
+}
+
+static void
+i3c_target_mctp_rx_packet_enqueue(struct i3c_device *i3cdev, const u8 *data, size_t count)
+{
+ struct i3c_target_mctp *priv = dev_get_drvdata(i3cdev_to_dev(i3cdev));
+ struct mctp_client *client;
+ struct mctp_packet *packet;
+ int ret;
+
+ spin_lock(&priv->client_lock);
+ client = priv->client;
+ if (client)
+ i3c_target_mctp_client_get(client);
+ spin_unlock(&priv->client_lock);
+
+ if (!client)
+ return;
+
+ packet = i3c_target_mctp_packet_alloc(count);
+ if (!packet)
+ goto err;
+
+ memcpy(packet->data, data, count);
+
+ ret = ptr_ring_produce(&client->rx_queue, packet);
+ if (ret)
+ i3c_target_mctp_packet_free(packet);
+ else
+ wake_up_all(&client->wait_queue);
+err:
+ i3c_target_mctp_client_put(client);
+}
+
+static struct mctp_client *i3c_target_mctp_create_client(struct i3c_target_mctp *priv)
+{
+ struct mctp_client *client;
+ int ret;
+
+ /* Currently, we support just one client. */
+ spin_lock_irq(&priv->client_lock);
+ ret = priv->client ? -EBUSY : 0;
+ spin_unlock_irq(&priv->client_lock);
+
+ if (ret)
+ return ERR_PTR(ret);
+
+ client = i3c_target_mctp_client_alloc(priv);
+ if (!client)
+ return ERR_PTR(-ENOMEM);
+
+ init_waitqueue_head(&client->wait_queue);
+
+ spin_lock_irq(&priv->client_lock);
+ priv->client = client;
+ spin_unlock_irq(&priv->client_lock);
+
+ return client;
+}
+
+static void i3c_target_mctp_delete_client(struct mctp_client *client)
+{
+ struct i3c_target_mctp *priv = client->priv;
+
+ spin_lock_irq(&priv->client_lock);
+ priv->client = NULL;
+ spin_unlock_irq(&priv->client_lock);
+
+ i3c_target_mctp_client_put(client);
+}
+
+static int i3c_target_mctp_open(struct inode *inode, struct file *file)
+{
+ struct i3c_target_mctp *priv = container_of(inode->i_cdev, struct i3c_target_mctp, cdev);
+ struct mctp_client *client;
+
+ client = i3c_target_mctp_create_client(priv);
+ if (IS_ERR(client))
+ return PTR_ERR(client);
+
+ file->private_data = client;
+
+ return 0;
+}
+
+static int i3c_target_mctp_release(struct inode *inode, struct file *file)
+{
+ struct mctp_client *client = file->private_data;
+
+ i3c_target_mctp_delete_client(client);
+
+ return 0;
+}
+
+static ssize_t i3c_target_mctp_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct mctp_client *client = file->private_data;
+ struct mctp_packet *rx_packet;
+
+ rx_packet = ptr_ring_consume_irq(&client->rx_queue);
+ if (!rx_packet)
+ return 0;
+
+ if (count < rx_packet->count) {
+ count = -EINVAL;
+ goto err_free;
+ }
+ if (count > rx_packet->count)
+ count = rx_packet->count;
+
+ if (copy_to_user(buf, rx_packet->data, count))
+ count = -EFAULT;
+err_free:
+ i3c_target_mctp_packet_free(rx_packet);
+
+ return count;
+}
+
+static ssize_t i3c_target_mctp_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct mctp_client *client = file->private_data;
+ struct i3c_target_mctp *priv = client->priv;
+ struct i3c_priv_xfer xfers[1] = {};
+ u8 *tx_data;
+ int ret;
+
+ tx_data = kzalloc(count, GFP_KERNEL);
+ if (!tx_data)
+ return -ENOMEM;
+
+ if (copy_from_user(tx_data, buf, count)) {
+ ret = -EFAULT;
+ goto out_packet;
+ }
+
+ xfers[0].data.out = tx_data;
+ xfers[0].len = count;
+
+ ret = i3c_device_do_priv_xfers(priv->i3cdev, xfers, ARRAY_SIZE(xfers));
+ if (ret)
+ goto out_packet;
+ ret = count;
+
+ /*
+ * TODO: Add support for IBI generation - it should be done only if IBI
+ * are enabled (the Active Controller may disabled them using CCC for
+ * that). Otherwise (if IBIs are disabled), we should make sure that when
+ * Active Controller issues GETSTATUS CCC the return value indicates
+ * that data is ready.
+ */
+out_packet:
+ kfree(tx_data);
+ return ret;
+}
+
+static __poll_t i3c_target_mctp_poll(struct file *file, struct poll_table_struct *pt)
+{
+ struct mctp_client *client = file->private_data;
+ __poll_t ret = 0;
+
+ poll_wait(file, &client->wait_queue, pt);
+
+ if (__ptr_ring_peek(&client->rx_queue))
+ ret |= EPOLLIN;
+
+ /*
+ * TODO: Add support for "write" readiness.
+ * DW-I3C has a hardware queue that has finite number of entries.
+ * If we try to issue more writes that space in this queue allows for,
+ * we're in trouble. This should be handled by error from write() and
+ * poll() blocking for write events.
+ */
+ return ret;
+}
+
+static const struct file_operations i3c_target_mctp_fops = {
+ .owner = THIS_MODULE,
+ .open = i3c_target_mctp_open,
+ .release = i3c_target_mctp_release,
+ .read = i3c_target_mctp_read,
+ .write = i3c_target_mctp_write,
+ .poll = i3c_target_mctp_poll,
+};
+
+static struct i3c_target_read_setup i3c_target_mctp_rx_packet_setup = {
+ .handler = i3c_target_mctp_rx_packet_enqueue,
+};
+
+static int i3c_target_mctp_probe(struct i3c_device *i3cdev)
+{
+ struct device *parent = i3cdev_to_dev(i3cdev);
+ struct i3c_target_mctp *priv;
+ struct device *dev;
+ int ret;
+
+ priv = devm_kzalloc(parent, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ ret = ida_alloc(&i3c_target_mctp_ida, GFP_KERNEL);
+ if (ret < 0)
+ return ret;
+ priv->id = ret;
+
+ priv->i3cdev = i3cdev;
+ spin_lock_init(&priv->client_lock);
+
+ cdev_init(&priv->cdev, &i3c_target_mctp_fops);
+ priv->cdev.owner = THIS_MODULE;
+ ret = cdev_add(&priv->cdev, i3c_target_mctp_devt, 1);
+ if (ret) {
+ ida_free(&i3c_target_mctp_ida, priv->id);
+ return ret;
+ }
+
+ dev = device_create(i3c_target_mctp_class, parent, i3c_target_mctp_devt,
+ NULL, "i3c-mctp-target-%d", priv->id);
+ if (IS_ERR(dev)) {
+ ret = PTR_ERR(dev);
+ goto err;
+ }
+
+ i3cdev_set_drvdata(i3cdev, priv);
+
+ i3c_target_read_register(i3cdev, &i3c_target_mctp_rx_packet_setup);
+
+ return 0;
+err:
+ cdev_del(&priv->cdev);
+ ida_free(&i3c_target_mctp_ida, priv->id);
+
+ return ret;
+}
+
+static void i3c_target_mctp_remove(struct i3c_device *i3cdev)
+{
+ struct i3c_target_mctp *priv = dev_get_drvdata(i3cdev_to_dev(i3cdev));
+
+ device_destroy(i3c_target_mctp_class, i3c_target_mctp_devt);
+ cdev_del(&priv->cdev);
+ ida_free(&i3c_target_mctp_ida, priv->id);
+}
+
+static const struct i3c_device_id i3c_target_mctp_ids[] = {
+ I3C_CLASS(0xcc, 0x0),
+ { },
+};
+
+static struct i3c_driver i3c_target_mctp_drv = {
+ .driver.name = "i3c-target-mctp",
+ .id_table = i3c_target_mctp_ids,
+ .probe = i3c_target_mctp_probe,
+ .remove = i3c_target_mctp_remove,
+ .target = true,
+};
+
+static int i3c_target_mctp_init(struct i3c_driver *drv)
+{
+ int ret;
+
+ ret = alloc_chrdev_region(&i3c_target_mctp_devt, 0,
+ I3C_TARGET_MCTP_MINORS, "i3c-target-mctp");
+ if (ret)
+ return ret;
+
+ i3c_target_mctp_class = class_create(THIS_MODULE, "i3c-target-mctp");
+ if (IS_ERR(i3c_target_mctp_class)) {
+ unregister_chrdev_region(i3c_target_mctp_devt, I3C_TARGET_MCTP_MINORS);
+ return PTR_ERR(i3c_target_mctp_class);
+ }
+
+ return i3c_driver_register(drv);
+}
+
+static void i3c_target_mctp_fini(struct i3c_driver *drv)
+{
+ i3c_driver_unregister(drv);
+ class_destroy(i3c_target_mctp_class);
+ unregister_chrdev_region(i3c_target_mctp_devt, I3C_TARGET_MCTP_MINORS);
+}
+
+module_driver(i3c_target_mctp_drv, i3c_target_mctp_init, i3c_target_mctp_fini);
+MODULE_AUTHOR("Iwona Winiarska <iwona.winiarska@intel.com>");
+MODULE_DESCRIPTION("I3C Target MCTP driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/mctp/Kconfig b/drivers/net/mctp/Kconfig
index dc71657d9184..ce9d2d2ccf3b 100644
--- a/drivers/net/mctp/Kconfig
+++ b/drivers/net/mctp/Kconfig
@@ -33,6 +33,15 @@ config MCTP_TRANSPORT_I2C
from DMTF specification DSP0237. A MCTP protocol network device is
created for each I2C bus that has been assigned a mctp-i2c device.
+config MCTP_TRANSPORT_I3C
+ tristate "MCTP I3C transport"
+ depends on I3C
+ help
+ Provides a driver to access MCTP devices over I3C transport,
+ from DMTF specification DSP0233.
+ A MCTP protocol network device is created for each I3C bus
+ having a "mctp-controller" devicetree property.
+
endmenu
endif
diff --git a/drivers/net/mctp/Makefile b/drivers/net/mctp/Makefile
index 1ca3e6028f77..e1cb99ced54a 100644
--- a/drivers/net/mctp/Makefile
+++ b/drivers/net/mctp/Makefile
@@ -1,2 +1,3 @@
obj-$(CONFIG_MCTP_SERIAL) += mctp-serial.o
obj-$(CONFIG_MCTP_TRANSPORT_I2C) += mctp-i2c.o
+obj-$(CONFIG_MCTP_TRANSPORT_I3C) += mctp-i3c.o
diff --git a/drivers/net/mctp/mctp-i3c.c b/drivers/net/mctp/mctp-i3c.c
new file mode 100644
index 000000000000..d1bd1d83aee2
--- /dev/null
+++ b/drivers/net/mctp/mctp-i3c.c
@@ -0,0 +1,777 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Implements DMTF specification
+ * "DSP0233 Management Component Transport Protocol (MCTP) I3C Transport
+ * Binding"
+ * https://www.dmtf.org/sites/default/files/standards/documents/DSP0233_1.0.0.pdf
+ *
+ * Copyright (c) 2023 Code Construct
+ */
+
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/i3c/device.h>
+#include <linux/i3c/master.h>
+#include <linux/if_arp.h>
+#include <net/mctp.h>
+#include <net/mctpdevice.h>
+
+#define MCTP_I3C_MAXBUF 65536
+/* 48 bit Provisioned Id */
+#define PID_SIZE 6
+
+/* 64 byte payload, 4 byte MCTP header */
+static const int MCTP_I3C_MINMTU = 64 + 4;
+/* One byte less to allow for the PEC */
+static const int MCTP_I3C_MAXMTU = MCTP_I3C_MAXBUF - 1;
+/* 4 byte MCTP header, no data, 1 byte PEC */
+static const int MCTP_I3C_MINLEN = 4 + 1;
+
+/* Sufficient for 64kB at min mtu */
+static const int MCTP_I3C_TX_QUEUE_LEN = 1100;
+
+/* Somewhat arbitrary */
+static const int MCTP_I3C_IBI_SLOTS = 8;
+
+/* Mandatory Data Byte in an IBI, from DSP0233 */
+static const u8 I3C_MDB_MCTP = 0xAE;
+/* From MIPI Device Characteristics Register (DCR) Assignments */
+static const u8 I3C_DCR_MCTP = 0xCC;
+
+static const char *MCTP_I3C_OF_PROP = "mctp-controller";
+
+/* List of mctp_i3c_busdev */
+static LIST_HEAD(busdevs);
+/* Protects busdevs, as well as mctp_i3c_bus.devs lists */
+static DEFINE_MUTEX(busdevs_lock);
+
+struct mctp_i3c_bus {
+ struct net_device *ndev;
+
+ struct task_struct *tx_thread;
+ wait_queue_head_t tx_wq;
+ /* tx_lock protects tx_skb and devs */
+ spinlock_t tx_lock;
+ /* Next skb to transmit */
+ struct sk_buff *tx_skb;
+ /* Scratch buffer for xmit */
+ u8 tx_scratch[MCTP_I3C_MAXBUF];
+
+ /* Element of busdevs */
+ struct list_head list;
+
+ /* Provisioned ID of our controller */
+ u64 pid;
+
+ struct i3c_bus *bus;
+ /* Head of mctp_i3c_device.list. Protected by busdevs_lock */
+ struct list_head devs;
+};
+
+struct mctp_i3c_device {
+ struct i3c_device *i3c;
+ struct mctp_i3c_bus *mbus;
+ struct list_head list; /* Element of mctp_i3c_bus.devs */
+
+ /* Held while tx_thread is using this device */
+ struct mutex lock;
+
+ /* Whether BCR indicates MDB is present in IBI */
+ bool have_mdb;
+ /* I3C dynamic address */
+ u8 addr;
+ /* Maximum read length */
+ u16 mrl;
+ /* Maximum write length */
+ u16 mwl;
+ /* Provisioned ID */
+ u64 pid;
+};
+
+/* We synthesise a mac header using the Provisioned ID.
+ * Used to pass dest to mctp_i3c_start_xmit.
+ */
+struct mctp_i3c_internal_hdr {
+ u8 dest[PID_SIZE];
+ u8 source[PID_SIZE];
+} __packed;
+
+/* Returns the 48 bit Provisioned Id from an i3c_device_info.pid */
+static void pid_to_addr(u64 pid, u8 addr[PID_SIZE])
+{
+ pid = cpu_to_be64(pid);
+ memcpy(addr, ((u8 *)&pid) + 2, PID_SIZE);
+}
+
+static u64 addr_to_pid(u8 addr[PID_SIZE])
+{
+ u64 pid = 0;
+
+ memcpy(((u8 *)&pid) + 2, addr, PID_SIZE);
+ return be64_to_cpu(pid);
+}
+
+static int mctp_i3c_read(struct mctp_i3c_device *mi)
+{
+ struct i3c_priv_xfer xfer = { .rnw = 1, .len = mi->mrl };
+ struct net_device_stats *stats = &mi->mbus->ndev->stats;
+ struct mctp_i3c_internal_hdr *ihdr = NULL;
+ struct sk_buff *skb = NULL;
+ struct mctp_skb_cb *cb;
+ int net_status, rc;
+ u8 pec, addr;
+
+ skb = netdev_alloc_skb(mi->mbus->ndev,
+ mi->mrl + sizeof(struct mctp_i3c_internal_hdr));
+ if (!skb) {
+ stats->rx_dropped++;
+ rc = -ENOMEM;
+ goto err;
+ }
+
+ skb->protocol = htons(ETH_P_MCTP);
+ /* Create a header for internal use */
+ skb_reset_mac_header(skb);
+ ihdr = skb_put(skb, sizeof(struct mctp_i3c_internal_hdr));
+ pid_to_addr(mi->pid, ihdr->source);
+ pid_to_addr(mi->mbus->pid, ihdr->dest);
+ skb_pull(skb, sizeof(struct mctp_i3c_internal_hdr));
+
+ xfer.data.in = skb_put(skb, mi->mrl);
+
+ rc = i3c_device_do_priv_xfers(mi->i3c, &xfer, 1);
+ if (rc < 0)
+ goto err;
+
+ if (WARN_ON_ONCE(xfer.len > mi->mrl)) {
+ /* Bad i3c bus driver */
+ rc = -EIO;
+ goto err;
+ }
+ if (xfer.len < MCTP_I3C_MINLEN) {
+ stats->rx_length_errors++;
+ rc = -EIO;
+ goto err;
+ }
+
+ /* check PEC, including address byte */
+ addr = mi->addr << 1 | 1;
+ pec = i2c_smbus_pec(0, &addr, 1);
+ pec = i2c_smbus_pec(pec, xfer.data.in, xfer.len - 1);
+ if (pec != ((u8 *)xfer.data.in)[xfer.len - 1]) {
+ stats->rx_crc_errors++;
+ rc = -EINVAL;
+ goto err;
+ }
+
+ /* Remove PEC */
+ skb_trim(skb, xfer.len - 1);
+
+ cb = __mctp_cb(skb);
+ cb->halen = PID_SIZE;
+ pid_to_addr(mi->pid, cb->haddr);
+
+ net_status = netif_rx(skb);
+
+ if (net_status == NET_RX_SUCCESS) {
+ stats->rx_packets++;
+ stats->rx_bytes += xfer.len - 1;
+ } else {
+ stats->rx_dropped++;
+ }
+
+ return 0;
+err:
+ kfree_skb(skb);
+ return rc;
+}
+
+static void mctp_i3c_ibi_handler(struct i3c_device *i3c,
+ const struct i3c_ibi_payload *payload)
+{
+ struct mctp_i3c_device *mi = i3cdev_get_drvdata(i3c);
+
+ if (WARN_ON_ONCE(!mi))
+ return;
+
+ if (mi->have_mdb) {
+ if (payload->len > 0) {
+ if (((u8 *)payload->data)[0] != I3C_MDB_MCTP) {
+ /* Not a mctp-i3c interrupt, ignore it */
+ return;
+ }
+ } else {
+ /* The BCR advertised a Mandatory Data Byte but the
+ * device didn't send one.
+ */
+ dev_warn_once(i3cdev_to_dev(i3c), "IBI with missing MDB");
+ }
+ }
+
+ mctp_i3c_read(mi);
+}
+
+static int mctp_i3c_setup(struct mctp_i3c_device *mi)
+{
+ bool ibi_set = false, ibi_enabled = false;
+ const struct i3c_ibi_setup ibi = {
+ .max_payload_len = 1,
+ .num_slots = MCTP_I3C_IBI_SLOTS,
+ .handler = mctp_i3c_ibi_handler,
+ };
+ struct i3c_device_info info;
+ int rc;
+
+ i3c_device_get_info(mi->i3c, &info);
+ mi->have_mdb = info.bcr & BIT(2);
+ mi->addr = info.dyn_addr;
+ mi->mwl = info.max_write_len;
+ mi->mrl = info.max_read_len;
+ mi->pid = info.pid;
+
+ rc = i3c_device_request_ibi(mi->i3c, &ibi);
+ if (rc == 0) {
+ ibi_set = true;
+ } else if (rc == -ENOTSUPP) {
+ /* This driver only supports In-Band Interrupt mode. Support for Polling Mode
+ * could be added if required.
+ */
+ dev_warn(i3cdev_to_dev(mi->i3c), "Failed, bus driver doesn't support In-Band Interrupts");
+ goto err;
+ } else {
+ dev_err(i3cdev_to_dev(mi->i3c), "Failed requesting IBI (%d)\n", rc);
+ goto err;
+ }
+
+ if (ibi_set) {
+ /* Device setup must be complete when IBI is enabled */
+ rc = i3c_device_enable_ibi(mi->i3c);
+ if (rc < 0) {
+ /* Assume a driver supporting request_ibi also supports enable_ibi */
+ dev_err(i3cdev_to_dev(mi->i3c), "Failed enabling IBI (%d)\n", rc);
+ goto err;
+ }
+ ibi_enabled = true;
+ }
+
+ return 0;
+err:
+ if (ibi_enabled)
+ i3c_device_disable_ibi(mi->i3c);
+ if (ibi_set)
+ i3c_device_free_ibi(mi->i3c);
+ return rc;
+}
+
+/* Adds a new MCTP i3c_device to a bus */
+static int mctp_i3c_add_device(struct mctp_i3c_bus *mbus, struct i3c_device *i3c)
+__must_hold(&busdevs_lock)
+{
+ struct mctp_i3c_device *mi = NULL;
+ int rc;
+
+ mi = kzalloc(sizeof(*mi), GFP_KERNEL);
+ if (!mi) {
+ rc = -ENOMEM;
+ goto err;
+ }
+ mi->mbus = mbus;
+ mi->i3c = i3c;
+ mutex_init(&mi->lock);
+ list_add(&mi->list, &mbus->devs);
+
+ i3cdev_set_drvdata(i3c, mi);
+ rc = mctp_i3c_setup(mi);
+ if (rc < 0)
+ goto err;
+
+ return 0;
+err:
+ dev_warn(i3cdev_to_dev(i3c), "Error adding mctp-i3c device, %d\n", rc);
+ if (mi) {
+ list_del(&mi->list);
+ kfree(mi);
+ }
+ return rc;
+}
+
+static int mctp_i3c_probe(struct i3c_device *i3c)
+{
+ struct mctp_i3c_bus *b = NULL, *mbus = NULL;
+ int rc;
+
+ /* Look for a known bus */
+ mutex_lock(&busdevs_lock);
+ list_for_each_entry(b, &busdevs, list)
+ if (b->bus == i3c->bus) {
+ mbus = b;
+ break;
+ }
+ mutex_unlock(&busdevs_lock);
+
+ if (!mbus) {
+ /* probably no "mctp-controller" property on the i3c bus */
+ return -ENODEV;
+ }
+
+ rc = mctp_i3c_add_device(mbus, i3c);
+ if (!rc)
+ goto err;
+
+ return 0;
+
+err:
+ return rc;
+}
+
+static void mctp_i3c_remove_device(struct mctp_i3c_device *mi)
+__must_hold(&busdevs_lock)
+{
+ /* Ensure the tx thread isn't using the device */
+ mutex_lock(&mi->lock);
+
+ /* Counterpart of mctp_i3c_setup */
+ i3c_device_disable_ibi(mi->i3c);
+ i3c_device_free_ibi(mi->i3c);
+
+ /* Counterpart of mctp_i3c_add_device */
+ i3cdev_set_drvdata(mi->i3c, NULL);
+ list_del(&mi->list);
+
+ /* Safe to unlock after removing from the list */
+ mutex_unlock(&mi->lock);
+ kfree(mi);
+}
+
+static void mctp_i3c_remove(struct i3c_device *i3c)
+{
+ struct mctp_i3c_device *mi = i3cdev_get_drvdata(i3c);
+
+ /* We my have received a Bus Remove notify prior to device remove,
+ * so mi will already be removed.
+ */
+ if (!mi)
+ return;
+
+ mutex_lock(&busdevs_lock);
+ mctp_i3c_remove_device(mi);
+ mutex_unlock(&busdevs_lock);
+}
+
+/* Returns the device for an address, with mi->lock held */
+static struct mctp_i3c_device *
+mctp_i3c_lookup(struct mctp_i3c_bus *mbus, u64 pid)
+{
+ struct mctp_i3c_device *mi = NULL, *ret = NULL;
+
+ mutex_lock(&busdevs_lock);
+ list_for_each_entry(mi, &mbus->devs, list)
+ if (mi->pid == pid) {
+ ret = mi;
+ mutex_lock(&mi->lock);
+ break;
+ }
+ mutex_unlock(&busdevs_lock);
+ return ret;
+}
+
+static void mctp_i3c_xmit(struct mctp_i3c_bus *mbus, struct sk_buff *skb)
+{
+ struct net_device_stats *stats = &mbus->ndev->stats;
+ struct i3c_priv_xfer xfer = { .rnw = false };
+ struct mctp_i3c_internal_hdr *ihdr = NULL;
+ struct mctp_i3c_device *mi = NULL;
+ unsigned int data_len;
+ u8 *data = NULL;
+ u8 addr, pec;
+ int rc = 0;
+ u64 pid;
+
+ skb_pull(skb, sizeof(struct mctp_i3c_internal_hdr));
+ data_len = skb->len;
+
+ ihdr = (void *)skb_mac_header(skb);
+
+ pid = addr_to_pid(ihdr->dest);
+ mi = mctp_i3c_lookup(mbus, pid);
+ if (!mi) {
+ /* I3C endpoint went away after the packet was enqueued? */
+ stats->tx_dropped++;
+ goto out;
+ }
+
+ if (WARN_ON_ONCE(data_len + 1 > MCTP_I3C_MAXBUF))
+ goto out;
+
+ if (data_len + 1 > (unsigned int)mi->mwl) {
+ /* Route MTU was larger than supported by the endpoint */
+ stats->tx_dropped++;
+ goto out;
+ }
+
+ /* Need a linear buffer with space for the PEC */
+ xfer.len = data_len + 1;
+ if (skb_tailroom(skb) >= 1) {
+ skb_put(skb, 1);
+ data = skb->data;
+ } else {
+ // TODO: test this
+ /* Otherwise need to copy the buffer */
+ skb_copy_bits(skb, 0, mbus->tx_scratch, skb->len);
+ data = mbus->tx_scratch;
+ }
+
+ /* PEC calculation */
+ addr = mi->addr << 1;
+ pec = i2c_smbus_pec(0, &addr, 1);
+ pec = i2c_smbus_pec(pec, data, data_len);
+ data[data_len] = pec;
+
+ xfer.data.out = data;
+ rc = i3c_device_do_priv_xfers(mi->i3c, &xfer, 1);
+ if (rc == 0) {
+ stats->tx_bytes += data_len;
+ stats->tx_packets++;
+ } else {
+ stats->tx_errors++;
+ }
+
+out:
+ if (mi)
+ mutex_unlock(&mi->lock);
+}
+
+static int mctp_i3c_tx_thread(void *data)
+{
+ struct mctp_i3c_bus *mbus = data;
+ struct sk_buff *skb;
+ unsigned long flags;
+
+ for (;;) {
+ if (kthread_should_stop())
+ break;
+
+ spin_lock_irqsave(&mbus->tx_lock, flags);
+ skb = mbus->tx_skb;
+ mbus->tx_skb = NULL;
+ spin_unlock_irqrestore(&mbus->tx_lock, flags);
+
+ if (netif_queue_stopped(mbus->ndev))
+ netif_wake_queue(mbus->ndev);
+
+ if (skb) {
+ mctp_i3c_xmit(mbus, skb);
+ kfree_skb(skb);
+ } else {
+ wait_event_idle(mbus->tx_wq,
+ mbus->tx_skb || kthread_should_stop());
+ }
+ }
+
+ return 0;
+}
+
+static netdev_tx_t mctp_i3c_start_xmit(struct sk_buff *skb,
+ struct net_device *ndev)
+{
+ struct mctp_i3c_bus *mbus = netdev_priv(ndev);
+ unsigned long flags;
+ netdev_tx_t ret;
+
+ spin_lock_irqsave(&mbus->tx_lock, flags);
+ netif_stop_queue(ndev);
+ if (mbus->tx_skb) {
+ dev_warn_ratelimited(&ndev->dev, "TX with queue stopped");
+ ret = NETDEV_TX_BUSY;
+ } else {
+ mbus->tx_skb = skb;
+ ret = NETDEV_TX_OK;
+ }
+ spin_unlock_irqrestore(&mbus->tx_lock, flags);
+
+ if (ret == NETDEV_TX_OK)
+ wake_up(&mbus->tx_wq);
+
+ return ret;
+}
+
+static void mctp_i3c_bus_free(struct mctp_i3c_bus *mbus)
+__must_hold(&busdevs_lock)
+{
+ struct mctp_i3c_device *mi = NULL, *tmp = NULL;
+
+ if (mbus->tx_thread) {
+ kthread_stop(mbus->tx_thread);
+ mbus->tx_thread = NULL;
+ }
+
+ /* Remove any child devices */
+ list_for_each_entry_safe(mi, tmp, &mbus->devs, list) {
+ mctp_i3c_remove_device(mi);
+ }
+
+ kfree_skb(mbus->tx_skb);
+ list_del(&mbus->list);
+}
+
+static void mctp_i3c_ndo_uninit(struct net_device *ndev)
+{
+ struct mctp_i3c_bus *mbus = netdev_priv(ndev);
+
+ /* Perform cleanup here to ensure there are no remaining references */
+ mctp_i3c_bus_free(mbus);
+}
+
+static int mctp_i3c_header_create(struct sk_buff *skb, struct net_device *dev,
+ unsigned short type, const void *daddr,
+ const void *saddr, unsigned int len)
+{
+ struct mctp_i3c_internal_hdr *ihdr;
+
+ skb_push(skb, sizeof(struct mctp_i3c_internal_hdr));
+ skb_reset_mac_header(skb);
+ ihdr = (void *)skb_mac_header(skb);
+ memcpy(ihdr->dest, daddr, PID_SIZE);
+ memcpy(ihdr->source, saddr, PID_SIZE);
+ return 0;
+}
+
+static const struct net_device_ops mctp_i3c_ops = {
+ .ndo_start_xmit = mctp_i3c_start_xmit,
+ .ndo_uninit = mctp_i3c_ndo_uninit,
+};
+
+static const struct header_ops mctp_i3c_headops = {
+ .create = mctp_i3c_header_create,
+};
+
+static void mctp_i3c_net_setup(struct net_device *dev)
+{
+ dev->type = ARPHRD_MCTP;
+
+ dev->mtu = MCTP_I3C_MAXMTU;
+ dev->min_mtu = MCTP_I3C_MINMTU;
+ dev->max_mtu = MCTP_I3C_MAXMTU;
+ dev->tx_queue_len = MCTP_I3C_TX_QUEUE_LEN;
+
+ dev->hard_header_len = sizeof(struct mctp_i3c_internal_hdr);
+ dev->addr_len = PID_SIZE;
+
+ dev->netdev_ops = &mctp_i3c_ops;
+ dev->header_ops = &mctp_i3c_headops;
+}
+
+static bool mctp_i3c_is_mctp_controller(struct i3c_bus *bus)
+{
+ struct i3c_dev_desc *master = bus->cur_master;
+
+ if (!master)
+ return false;
+
+ return of_property_read_bool(master->common.master->dev.of_node, MCTP_I3C_OF_PROP);
+}
+
+/* Returns the Provisioned Id of a local bus master */
+static int mctp_i3c_bus_local_pid(struct i3c_bus *bus, u64 *ret_pid)
+{
+ struct i3c_dev_desc *master;
+
+ master = bus->cur_master;
+ if (WARN_ON_ONCE(!master))
+ return -ENOENT;
+ *ret_pid = master->info.pid;
+
+ return 0;
+}
+
+/* Returns an ERR_PTR on failure */
+static struct mctp_i3c_bus *mctp_i3c_bus_add(struct i3c_bus *bus)
+__must_hold(&busdevs_lock)
+{
+ struct mctp_i3c_bus *mbus = NULL;
+ struct net_device *ndev = NULL;
+ char namebuf[IFNAMSIZ];
+ u8 addr[PID_SIZE];
+ int rc;
+
+ if (!mctp_i3c_is_mctp_controller(bus))
+ return ERR_PTR(-ENOENT);
+
+ snprintf(namebuf, sizeof(namebuf), "mctpi3c%d", bus->id);
+ ndev = alloc_netdev(sizeof(*mbus), namebuf, NET_NAME_ENUM, mctp_i3c_net_setup);
+ if (!ndev) {
+ rc = -ENOMEM;
+ goto err;
+ }
+ dev_net_set(ndev, current->nsproxy->net_ns);
+
+ mbus = netdev_priv(ndev);
+ mbus->ndev = ndev;
+ mbus->bus = bus;
+ INIT_LIST_HEAD(&mbus->devs);
+ list_add(&mbus->list, &busdevs);
+
+ rc = mctp_i3c_bus_local_pid(bus, &mbus->pid);
+ if (rc < 0) {
+ dev_err(&ndev->dev, "No I3C PID available\n");
+ goto err;
+ }
+ pid_to_addr(mbus->pid, addr);
+ dev_addr_set(ndev, addr);
+
+ init_waitqueue_head(&mbus->tx_wq);
+ spin_lock_init(&mbus->tx_lock);
+ mbus->tx_thread = kthread_create(mctp_i3c_tx_thread, mbus,
+ "%s/tx", ndev->name);
+ if (IS_ERR(mbus->tx_thread)) {
+ dev_warn(&ndev->dev, "Error creating thread: %pe\n",
+ mbus->tx_thread);
+ rc = PTR_ERR(mbus->tx_thread);
+ mbus->tx_thread = NULL;
+ goto err;
+ }
+ wake_up_process(mbus->tx_thread);
+
+ rc = mctp_register_netdev(ndev, NULL);
+ if (rc < 0) {
+ dev_warn(&ndev->dev, "netdev register failed: %d\n", rc);
+ goto err;
+ }
+ return mbus;
+err:
+ /* uninit will not get called if a netdev has not been registered,
+ * so we perform the same mbus cleanup manually.
+ */
+ if (mbus)
+ mctp_i3c_bus_free(mbus);
+ if (ndev)
+ free_netdev(ndev);
+ return ERR_PTR(rc);
+}
+
+static void mctp_i3c_bus_remove(struct mctp_i3c_bus *mbus)
+__must_hold(&busdevs_lock)
+{
+ /* Unregister calls through to ndo_uninit -> mctp_i3c_bus_free() */
+ mctp_unregister_netdev(mbus->ndev);
+
+ free_netdev(mbus->ndev);
+ /* mbus is deallocated */
+}
+
+/* Removes all mctp-i3c busses */
+static void mctp_i3c_bus_remove_all(void)
+{
+ struct mctp_i3c_bus *mbus = NULL, *tmp = NULL;
+
+ mutex_lock(&busdevs_lock);
+ list_for_each_entry_safe(mbus, tmp, &busdevs, list) {
+ mctp_i3c_bus_remove(mbus);
+ }
+ mutex_unlock(&busdevs_lock);
+}
+
+/* Adds a i3c_bus if it isn't already in the busdevs list.
+ * Suitable as an i3c_for_each_bus_locked callback.
+ */
+static int mctp_i3c_bus_add_new(struct i3c_bus *bus, void *data)
+{
+ struct mctp_i3c_bus *mbus = NULL, *tmp = NULL;
+ bool exists = false;
+
+ mutex_lock(&busdevs_lock);
+ list_for_each_entry_safe(mbus, tmp, &busdevs, list)
+ if (mbus->bus == bus)
+ exists = true;
+
+ /* It is OK for a bus to already exist. That can occur due to
+ * the race in mod_init between notifier and for_each_bus
+ */
+ if (!exists)
+ mctp_i3c_bus_add(bus);
+ mutex_unlock(&busdevs_lock);
+ return 0;
+}
+
+static void mctp_i3c_notify_bus_remove(struct i3c_bus *bus)
+{
+ struct mctp_i3c_bus *mbus = NULL, *tmp;
+
+ mutex_lock(&busdevs_lock);
+ list_for_each_entry_safe(mbus, tmp, &busdevs, list)
+ if (mbus->bus == bus)
+ mctp_i3c_bus_remove(mbus);
+ mutex_unlock(&busdevs_lock);
+}
+
+static int mctp_i3c_notifier_call(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ switch (action) {
+ case I3C_NOTIFY_BUS_ADD:
+ mctp_i3c_bus_add_new((struct i3c_bus *)data, NULL);
+ break;
+ case I3C_NOTIFY_BUS_REMOVE:
+ mctp_i3c_notify_bus_remove((struct i3c_bus *)data);
+ break;
+ }
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block mctp_i3c_notifier = {
+ .notifier_call = mctp_i3c_notifier_call,
+};
+
+static const struct i3c_device_id mctp_i3c_ids[] = {
+ I3C_CLASS(I3C_DCR_MCTP, NULL),
+ { 0 },
+};
+
+static struct i3c_driver mctp_i3c_driver = {
+ .driver = {
+ .name = "mctp-i3c",
+ },
+ .probe = mctp_i3c_probe,
+ .remove = mctp_i3c_remove,
+ .id_table = mctp_i3c_ids,
+};
+
+static __init int mctp_i3c_mod_init(void)
+{
+ int rc;
+
+ rc = i3c_register_notifier(&mctp_i3c_notifier);
+ if (rc < 0) {
+ i3c_driver_unregister(&mctp_i3c_driver);
+ return rc;
+ }
+
+ i3c_for_each_bus_locked(mctp_i3c_bus_add_new, NULL);
+
+ rc = i3c_driver_register(&mctp_i3c_driver);
+ if (rc < 0)
+ return rc;
+
+ return 0;
+}
+
+static __exit void mctp_i3c_mod_exit(void)
+{
+ int rc;
+
+ i3c_driver_unregister(&mctp_i3c_driver);
+
+ rc = i3c_unregister_notifier(&mctp_i3c_notifier);
+ if (rc < 0)
+ pr_warn("MCTP I3C could not unregister notifier, %d\n", rc);
+
+ mctp_i3c_bus_remove_all();
+}
+
+module_init(mctp_i3c_mod_init);
+module_exit(mctp_i3c_mod_exit);
+
+MODULE_DEVICE_TABLE(i3c, mctp_i3c_ids);
+MODULE_DESCRIPTION("MCTP I3C device");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Matt Johnston <matt@codeconstruct.com.au>");
diff --git a/include/linux/i3c/ccc.h b/include/linux/i3c/ccc.h
index 73b0982cc519..c531bae59380 100644
--- a/include/linux/i3c/ccc.h
+++ b/include/linux/i3c/ccc.h
@@ -32,6 +32,8 @@
#define I3C_CCC_DEFSLVS I3C_CCC_ID(0x8, true)
#define I3C_CCC_ENTTM I3C_CCC_ID(0xb, true)
#define I3C_CCC_ENTHDR(x) I3C_CCC_ID(0x20 + (x), true)
+#define I3C_CCC_SETAASA I3C_CCC_ID(0x29, true)
+#define I3C_CCC_SETHID I3C_CCC_ID(0x61, true)
/* Unicast-only commands */
#define I3C_CCC_SETDASA I3C_CCC_ID(0x7, false)
@@ -243,6 +245,15 @@ struct i3c_ccc_setbrgtgt {
struct i3c_ccc_bridged_slave_desc bslaves[0];
} __packed;
+
+/**
+ * struct i3c_ccc_sethid - payload passed to SETHID CCC
+ *
+ * @hid: 3-bit HID
+ */
+struct i3c_ccc_sethid {
+ u8 hid;
+};
/**
* enum i3c_sdr_max_data_rate - max data rate values for private SDR transfers
*/
diff --git a/include/linux/i3c/device.h b/include/linux/i3c/device.h
index 8242e13e7b0b..3e6ee6817d81 100644
--- a/include/linux/i3c/device.h
+++ b/include/linux/i3c/device.h
@@ -74,6 +74,7 @@ struct i3c_priv_xfer {
*/
enum i3c_dcr {
I3C_DCR_GENERIC_DEVICE = 0,
+ I3C_DCR_HUB = 0xC2,
};
#define I3C_PID_MANUF_ID(pid) (((pid) & GENMASK_ULL(47, 33)) >> 33)
@@ -128,6 +129,7 @@ struct i3c_device_info {
u32 max_read_turnaround;
u16 max_read_len;
u16 max_write_len;
+ __be16 status;
};
/*
@@ -178,6 +180,7 @@ struct i3c_driver {
int (*probe)(struct i3c_device *dev);
void (*remove)(struct i3c_device *dev);
const struct i3c_device_id *id_table;
+ bool target;
};
static inline struct i3c_driver *drv_to_i3cdrv(struct device_driver *drv)
@@ -293,6 +296,8 @@ int i3c_device_do_priv_xfers(struct i3c_device *dev,
struct i3c_priv_xfer *xfers,
int nxfers);
+int i3c_device_generate_ibi(struct i3c_device *dev, const u8 *data, int len);
+
void i3c_device_get_info(struct i3c_device *dev, struct i3c_device_info *info);
struct i3c_ibi_payload {
@@ -331,5 +336,19 @@ int i3c_device_request_ibi(struct i3c_device *dev,
void i3c_device_free_ibi(struct i3c_device *dev);
int i3c_device_enable_ibi(struct i3c_device *dev);
int i3c_device_disable_ibi(struct i3c_device *dev);
+int i3c_device_send_ccc_cmd(struct i3c_device *dev, u8 ccc_id);
+
+int i3c_device_getstatus_ccc(struct i3c_device *dev, struct i3c_device_info *info);
+int i3c_device_setmrl_ccc(struct i3c_device *dev, struct i3c_device_info *info, __be16 read_len,
+ u8 ibi_len);
+int i3c_device_setmwl_ccc(struct i3c_device *dev, struct i3c_device_info *info, __be16 write_len);
+int i3c_device_getmrl_ccc(struct i3c_device *dev, struct i3c_device_info *info);
+int i3c_device_getmwl_ccc(struct i3c_device *dev, struct i3c_device_info *info);
+
+struct i3c_target_read_setup {
+ void (*handler)(struct i3c_device *dev, const u8 *data, size_t len);
+};
+
+int i3c_target_read_register(struct i3c_device *dev, const struct i3c_target_read_setup *setup);
#endif /* I3C_DEV_H */
diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h
index 9cb39d901cd5..a449c5ebe735 100644
--- a/include/linux/i3c/master.h
+++ b/include/linux/i3c/master.h
@@ -22,6 +22,14 @@
#define I3C_BROADCAST_ADDR 0x7e
#define I3C_MAX_ADDR GENMASK(6, 0)
+struct i3c_target_ops;
+
+/* notifier actions. notifier call data is the struct i3c_bus */
+enum {
+ I3C_NOTIFY_BUS_ADD,
+ I3C_NOTIFY_BUS_REMOVE,
+};
+
struct i3c_master_controller;
struct i3c_bus;
struct i2c_device;
@@ -184,11 +192,21 @@ struct i3c_dev_boardinfo {
struct device_node *of_node;
};
+/**
+ * struct i3c_target_info - target information attached to a specific device
+ * @read handler: handler specified at i3c_target_read_register() call time.
+ */
+
+struct i3c_target_info {
+ void (*read_handler)(struct i3c_device *dev, const u8 *data, size_t len);
+};
+
/**
* struct i3c_dev_desc - I3C device descriptor
* @common: common part of the I3C device descriptor
* @info: I3C device information. Will be automatically filled when you create
* your device with i3c_master_add_i3c_dev_locked()
+ * @target_info: I3C target information.
* @ibi_lock: lock used to protect the &struct_i3c_device->ibi
* @ibi: IBI info attached to a device. Should be NULL until
* i3c_device_request_ibi() is called
@@ -207,6 +225,7 @@ struct i3c_dev_boardinfo {
struct i3c_dev_desc {
struct i3c_i2c_dev_desc common;
struct i3c_device_info info;
+ struct i3c_target_info target_info;
struct mutex ibi_lock;
struct i3c_device_ibi_info *ibi;
struct i3c_device *dev;
@@ -323,6 +342,7 @@ enum i3c_addr_slot_status {
* the same master in parallel. This is the responsibility of the
* master to guarantee that frames are actually sent sequentially and
* not interlaced
+ * @jesd403: true if the bus is JESD403 compliant.
*
* The I3C bus is represented with its own object and not implicitly described
* by the I3C master to cope with the multi-master functionality, where one bus
@@ -343,6 +363,7 @@ struct i3c_bus {
struct list_head i2c;
} devs;
struct rw_semaphore lock;
+ bool jesd403;
};
/**
@@ -463,6 +484,8 @@ struct i3c_master_controller_ops {
* registered to the I2C subsystem to be as transparent as possible to
* existing I2C drivers
* @ops: master operations. See &struct i3c_master_controller_ops
+ * @target_ops: target operations. See &struct i3c_target_ops
+ * @target: true if the underlying I3C device acts as a target on I3C bus
* @secondary: true if the master is a secondary master
* @init_done: true when the bus initialization is done
* @boardinfo.i3c: list of I3C boardinfo objects
@@ -485,6 +508,8 @@ struct i3c_master_controller {
struct i3c_dev_desc *this;
struct i2c_adapter i2c;
const struct i3c_master_controller_ops *ops;
+ const struct i3c_target_ops *target_ops;
+ unsigned int target : 1;
unsigned int secondary : 1;
unsigned int init_done : 1;
struct {
@@ -544,6 +569,13 @@ int i3c_master_register(struct i3c_master_controller *master,
bool secondary);
int i3c_master_unregister(struct i3c_master_controller *master);
+int i3c_register(struct i3c_master_controller *master,
+ struct device *parent,
+ const struct i3c_master_controller_ops *master_ops,
+ const struct i3c_target_ops *target_ops,
+ bool secondary);
+int i3c_unregister(struct i3c_master_controller *master);
+
/**
* i3c_dev_get_master_data() - get master private data attached to an I3C
* device descriptor
@@ -652,4 +684,9 @@ void i3c_master_queue_ibi(struct i3c_dev_desc *dev, struct i3c_ibi_slot *slot);
struct i3c_ibi_slot *i3c_master_get_free_ibi_slot(struct i3c_dev_desc *dev);
+void i3c_for_each_bus_locked(int (*fn)(struct i3c_bus *bus, void *data),
+ void *data);
+int i3c_register_notifier(struct notifier_block *nb);
+int i3c_unregister_notifier(struct notifier_block *nb);
+
#endif /* I3C_MASTER_H */
diff --git a/include/linux/i3c/mctp/i3c-mctp.h b/include/linux/i3c/mctp/i3c-mctp.h
new file mode 100644
index 000000000000..596759faa87d
--- /dev/null
+++ b/include/linux/i3c/mctp/i3c-mctp.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2022 Intel Corporation.*/
+
+#ifndef I3C_MCTP_H
+#define I3C_MCTP_H
+
+#define I3C_MCTP_PACKET_SIZE 255
+#define I3C_MCTP_PAYLOAD_SIZE 251
+#define I3C_MCTP_HDR_SIZE 4
+
+/* PECI MCTP Intel VDM definitions */
+#define MCTP_MSG_TYPE_VDM_PCI 0x7E
+#define MCTP_VDM_PCI_INTEL_VENDOR_ID 0x8086
+#define MCTP_VDM_PCI_INTEL_PECI 0x2
+
+/* MCTP message header offsets */
+#define MCTP_MSG_HDR_MSG_TYPE_OFFSET 0
+#define MCTP_MSG_HDR_VENDOR_OFFSET 1
+#define MCTP_MSG_HDR_OPCODE_OFFSET 4
+
+struct i3c_mctp_client;
+
+struct mctp_protocol_hdr {
+ u8 ver;
+ u8 dest;
+ u8 src;
+ u8 flags_seq_tag;
+} __packed;
+
+struct i3c_mctp_packet_data {
+ u8 protocol_hdr[I3C_MCTP_HDR_SIZE];
+ u8 payload[I3C_MCTP_PAYLOAD_SIZE];
+};
+
+struct i3c_mctp_packet {
+ struct i3c_mctp_packet_data data;
+ u32 size;
+};
+
+void *i3c_mctp_packet_alloc(gfp_t flags);
+void i3c_mctp_packet_free(void *packet);
+
+int i3c_mctp_get_eid(struct i3c_mctp_client *client, u8 domain_id, u8 *eid);
+int i3c_mctp_send_packet(struct i3c_device *i3c, struct i3c_mctp_packet *tx_packet);
+struct i3c_mctp_packet *i3c_mctp_receive_packet(struct i3c_mctp_client *client,
+ unsigned long timeout);
+struct i3c_mctp_client *i3c_mctp_add_peci_client(struct i3c_device *i3c);
+void i3c_mctp_remove_peci_client(struct i3c_mctp_client *client);
+
+#endif /* I3C_MCTP_H */
diff --git a/include/linux/i3c/target.h b/include/linux/i3c/target.h
new file mode 100644
index 000000000000..9e71124b5325
--- /dev/null
+++ b/include/linux/i3c/target.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2022, Intel Corporation */
+
+#ifndef I3C_TARGET_H
+#define I3C_TARGET_H
+
+#include <linux/device.h>
+#include <linux/i3c/device.h>
+
+struct i3c_master_controller;
+
+struct i3c_target_ops {
+ int (*bus_init)(struct i3c_master_controller *master);
+ void (*bus_cleanup)(struct i3c_master_controller *master);
+ int (*priv_xfers)(struct i3c_dev_desc *dev, struct i3c_priv_xfer *xfers, int nxfers);
+ int (*generate_ibi)(struct i3c_dev_desc *dev, const u8 *data, int len);
+};
+
+int i3c_target_register(struct i3c_master_controller *master, struct device *parent,
+ const struct i3c_target_ops *ops);
+int i3c_target_unregister(struct i3c_master_controller *master);
+
+#endif
diff --git a/include/uapi/linux/i3c/i3cdev.h b/include/uapi/linux/i3c/i3cdev.h
new file mode 100644
index 000000000000..0897313f5516
--- /dev/null
+++ b/include/uapi/linux/i3c/i3cdev.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Copyright (c) 2019 Synopsys, Inc. and/or its affiliates.
+ *
+ * Author: Vitor Soares <vitor.soares@synopsys.com>
+ */
+
+#ifndef _UAPI_I3C_DEV_H_
+#define _UAPI_I3C_DEV_H_
+
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+/* IOCTL commands */
+#define I3C_DEV_IOC_MAGIC 0x07
+
+/**
+ * struct i3c_ioc_priv_xfer - I3C SDR ioctl private transfer
+ * @data: Holds pointer to userspace buffer with transmit data.
+ * @len: Length of data buffer buffers, in bytes.
+ * @rnw: encodes the transfer direction. true for a read, false for a write
+ */
+struct i3c_ioc_priv_xfer {
+ __u64 data;
+ __u16 len;
+ __u8 rnw;
+ __u8 pad[5];
+};
+
+
+#define I3C_PRIV_XFER_SIZE(N) \
+ ((((sizeof(struct i3c_ioc_priv_xfer)) * (N)) < (1 << _IOC_SIZEBITS)) \
+ ? ((sizeof(struct i3c_ioc_priv_xfer)) * (N)) : 0)
+
+#define I3C_IOC_PRIV_XFER(N) \
+ _IOC(_IOC_READ|_IOC_WRITE, I3C_DEV_IOC_MAGIC, 30, I3C_PRIV_XFER_SIZE(N))
+
+#endif
diff --git a/net/mctp/af_mctp.c b/net/mctp/af_mctp.c
index 786079e6d8cf..eb8fbc128104 100644
--- a/net/mctp/af_mctp.c
+++ b/net/mctp/af_mctp.c
@@ -560,15 +560,9 @@ static void mctp_sk_unhash(struct sock *sk)
spin_lock_irqsave(&key->lock, fl2);
__mctp_key_remove(key, net, fl2, MCTP_TRACE_KEY_CLOSED);
}
- sock_set_flag(sk, SOCK_DEAD);
spin_unlock_irqrestore(&net->mctp.keys_lock, flags);
}
-static void mctp_sk_destruct(struct sock *sk)
-{
- skb_queue_purge(&sk->sk_receive_queue);
-}
-
static struct proto mctp_proto = {
.name = "MCTP",
.owner = THIS_MODULE,
@@ -605,7 +599,6 @@ static int mctp_pf_create(struct net *net, struct socket *sock,
return -ENOMEM;
sock_init_data(sock, sk);
- sk->sk_destruct = mctp_sk_destruct;
rc = 0;
if (sk->sk_prot->init)
diff --git a/net/mctp/route.c b/net/mctp/route.c
index b9a293c1c49c..0945e679a92b 100644
--- a/net/mctp/route.c
+++ b/net/mctp/route.c
@@ -177,11 +177,6 @@ static int mctp_key_add(struct mctp_sk_key *key, struct mctp_sock *msk)
spin_lock_irqsave(&net->mctp.keys_lock, flags);
- if (sock_flag(&msk->sk, SOCK_DEAD)) {
- rc = -EINVAL;
- goto out_unlock;
- }
-
hlist_for_each_entry(tmp, &net->mctp.keys, hlist) {
if (mctp_key_match(tmp, key->local_addr, key->peer_addr,
key->tag)) {
@@ -203,7 +198,6 @@ static int mctp_key_add(struct mctp_sk_key *key, struct mctp_sock *msk)
hlist_add_head(&key->sklist, &msk->keys);
}
-out_unlock:
spin_unlock_irqrestore(&net->mctp.keys_lock, flags);
return rc;
--
2.34.1