This patch introduces support for I3C target mode, which is referenced
with a PCIe Endpoint system. It also establishes a configuration framework
(configfs) for the I3C target controller driver and the I3C target function
driver
Typic usage as
The user can configure the i3c-target-tty device using configfs entry. In
order to change the vendorid, the following commands can be used
# echo 0x011b > functions/tty/func1/vendor_id
# echo 0x1000 > functions/tty/func1/part_id
# echo 0x6 > functions/tty/t/bcr
Binding i3c-target-tty Device to target Controller
------------------------------------------------
In order for the target function device to be useful, it has to be bound to
a I3C target controller driver. Use the configfs to bind the function
device to one of the controller driver present in the system::
# ln -s functions/pci_epf_test/func1 controllers/44330000.i3c-target/
Host side:
cat /dev/ttyI3C0
Taret side:
echo abc >/dev/ttyI3C0
Chagne from v2 to v3
- using 'mode' distingiush master and target.
- move svc-i3c-target.c to under master,
- built together with svc-i3c-master.c
Change from v1 to v2
- change "slave" to "target"
- include master side tty patch
- fixed dtbcheck problem
- fixed kerne-doc check warning
Some review comment may be lost since it is quite long time since v1. Now
master side dependent patches already in linux-next. So sent target side
patches with tty support again.
No sure why an additional "\r\n" appended.
Frank Li (8):
i3c: add target mode support
dt-bindings: i3c: svc: add proptery mode
Documentation: i3c: Add I3C target mode controller and function
i3c: svc: Add svc-i3c-main.c and svc-i3c.h
i3c: target: add svc target controller support
i3c: target: func: add tty driver
i3c: add API i3c_dev_gettstatus_format1() to get target device status
tty: i3c: add TTY over I3C master support
.../bindings/i3c/silvaco,i3c-master.yaml | 11 +-
Documentation/driver-api/i3c/index.rst | 1 +
.../driver-api/i3c/target/i3c-target-cfs.rst | 109 +++
.../driver-api/i3c/target/i3c-target.rst | 189 +++++
.../driver-api/i3c/target/i3c-tty-howto.rst | 109 +++
Documentation/driver-api/i3c/target/index.rst | 13 +
drivers/i3c/Kconfig | 31 +-
drivers/i3c/Makefile | 3 +
drivers/i3c/device.c | 24 +
drivers/i3c/func/Kconfig | 9 +
drivers/i3c/func/Makefile | 3 +
drivers/i3c/func/tty.c | 474 +++++++++++
drivers/i3c/i3c-cfs.c | 389 +++++++++
drivers/i3c/internals.h | 1 +
drivers/i3c/master.c | 26 +
drivers/i3c/master/Makefile | 3 +-
drivers/i3c/master/svc-i3c-main.c | 80 ++
drivers/i3c/master/svc-i3c-master.c | 34 +-
drivers/i3c/master/svc-i3c-target.c | 776 ++++++++++++++++++
drivers/i3c/master/svc-i3c.h | 15 +
drivers/i3c/target.c | 453 ++++++++++
drivers/tty/Kconfig | 13 +
drivers/tty/Makefile | 1 +
drivers/tty/i3c_tty.c | 428 ++++++++++
include/linux/i3c/device.h | 1 +
include/linux/i3c/target.h | 548 +++++++++++++
26 files changed, 3713 insertions(+), 31 deletions(-)
create mode 100644 Documentation/driver-api/i3c/target/i3c-target-cfs.rst
create mode 100644 Documentation/driver-api/i3c/target/i3c-target.rst
create mode 100644 Documentation/driver-api/i3c/target/i3c-tty-howto.rst
create mode 100644 Documentation/driver-api/i3c/target/index.rst
create mode 100644 drivers/i3c/func/Kconfig
create mode 100644 drivers/i3c/func/Makefile
create mode 100644 drivers/i3c/func/tty.c
create mode 100644 drivers/i3c/i3c-cfs.c
create mode 100644 drivers/i3c/master/svc-i3c-main.c
create mode 100644 drivers/i3c/master/svc-i3c-target.c
create mode 100644 drivers/i3c/master/svc-i3c.h
create mode 100644 drivers/i3c/target.c
create mode 100644 drivers/tty/i3c_tty.c
create mode 100644 include/linux/i3c/target.h
--
2.34.1
Add 'mode' property to distinguish between 'controller' and 'target' modes.
Reviewed-by: Krzysztof Kozlowski <[email protected]>
Signed-off-by: Frank Li <[email protected]>
---
Notes:
Change from v4 to v5
- add Krzysztof Kozlowski's review tag
Change from v3 to v4
-fix dtb check error
Change from v2 to v3
- using 'mode' distringuish master and target mode
.../devicetree/bindings/i3c/silvaco,i3c-master.yaml | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/i3c/silvaco,i3c-master.yaml b/Documentation/devicetree/bindings/i3c/silvaco,i3c-master.yaml
index 133855f11b4f5..3af77d143f018 100644
--- a/Documentation/devicetree/bindings/i3c/silvaco,i3c-master.yaml
+++ b/Documentation/devicetree/bindings/i3c/silvaco,i3c-master.yaml
@@ -4,7 +4,7 @@
$id: http://devicetree.org/schemas/i3c/silvaco,i3c-master.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
-title: Silvaco I3C master
+title: Silvaco I3C master/target
maintainers:
- Conor Culhane <[email protected]>
@@ -22,6 +22,15 @@ properties:
interrupts:
maxItems: 1
+ mode:
+ description:
+ Tells Dual-Role I3C controllers that we want to work on a particular
+ mode. In case this attribute isn't passed via DT, I3C controllers
+ should default to 'controller'.
+ $ref: /schemas/types.yaml#/definitions/string
+ enum: [controller, target]
+ default: controller
+
clocks:
items:
- description: system clock
--
2.34.1
Introduce a new target core layer in order to support target functions in
linux kernel. This comprises the controller library and function library.
Controller library implements functions specific to an target controller
and function library implements functions specific to an target function.
Introduce a new configfs entry to configure the target function configuring
and bind the target function with target controller.
Signed-off-by: Frank Li <[email protected]>
---
Notes:
Change from v5 to v6
- fixed build error when have not CONFIG_TARGET_CONFIGFS
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/
Chagne from v4 to v5
- add include <linux/slab.h> to fix build error
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/
Chagne from v4 to v8
- add include <linux/slab.h> to fix build error
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/
drivers/i3c/Kconfig | 28 +-
drivers/i3c/Makefile | 2 +
drivers/i3c/i3c-cfs.c | 389 ++++++++++++++++++++++++++
drivers/i3c/target.c | 453 ++++++++++++++++++++++++++++++
include/linux/i3c/target.h | 548 +++++++++++++++++++++++++++++++++++++
5 files changed, 1419 insertions(+), 1 deletion(-)
create mode 100644 drivers/i3c/i3c-cfs.c
create mode 100644 drivers/i3c/target.c
create mode 100644 include/linux/i3c/target.h
diff --git a/drivers/i3c/Kconfig b/drivers/i3c/Kconfig
index 30a441506f61c..d59a7eb83d13a 100644
--- a/drivers/i3c/Kconfig
+++ b/drivers/i3c/Kconfig
@@ -10,7 +10,7 @@ menuconfig I3C
support for high speed transfers and native interrupt support
without the need for extra pins.
- The I3C protocol also standardizes the slave device types and is
+ The I3C protocol also standardizes the target device types and is
mainly designed to communicate with sensors.
If you want I3C support, you should say Y here and also to the
@@ -22,3 +22,29 @@ menuconfig I3C
if I3C
source "drivers/i3c/master/Kconfig"
endif # I3C
+
+config I3C_TARGET
+ bool "I3C Target Support"
+ help
+ Support I3C Target Mode.
+
+ Enable this configuration option to support configurable I3C target.
+ This should be enabled if the platform has a I3C controller that can
+ operate in target mode.
+
+ Enabling this option will build the I3C target library, which includes
+ target controller library and target function library.
+
+ If in doubt, say "N" to disable target support.
+
+config I3C_TARGET_CONFIGFS
+ bool "I3C Target Configfs Support"
+ depends on I3C_TARGET
+ select CONFIGFS_FS
+ help
+ Configfs entry for target function and controller.
+
+ This will enable the configfs entry that can be used to configure
+ the target function and used to bind the function with a target
+ controller.
+
diff --git a/drivers/i3c/Makefile b/drivers/i3c/Makefile
index 11982efbc6d91..c275aeae8970c 100644
--- a/drivers/i3c/Makefile
+++ b/drivers/i3c/Makefile
@@ -1,4 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
i3c-y := device.o master.o
obj-$(CONFIG_I3C) += i3c.o
+obj-$(CONFIG_I3C_TARGET) += target.o
+obj-$(CONFIG_I3C_TARGET_CONFIGFS) += i3c-cfs.o
obj-$(CONFIG_I3C) += master/
diff --git a/drivers/i3c/i3c-cfs.c b/drivers/i3c/i3c-cfs.c
new file mode 100644
index 0000000000000..a2f0f44fcdaea
--- /dev/null
+++ b/drivers/i3c/i3c-cfs.c
@@ -0,0 +1,389 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Configfs to configure the I3C Slave
+ *
+ * Copyright (C) 2023 NXP
+ * Author: Frank Li <[email protected]>
+ */
+
+#include <linux/configfs.h>
+#include <linux/module.h>
+#include <linux/i3c/target.h>
+#include <linux/idr.h>
+#include <linux/slab.h>
+
+static DEFINE_MUTEX(functions_mutex);
+static struct config_group *functions_group;
+static struct config_group *controllers_group;
+
+struct i3c_target_func_group {
+ struct config_group group;
+ struct i3c_target_func *func;
+};
+
+struct i3c_target_ctrl_group {
+ struct config_group group;
+ struct i3c_target_ctrl *ctrl;
+};
+
+static inline struct i3c_target_func_group *to_i3c_target_func_group(struct config_item *item)
+{
+ return container_of(to_config_group(item), struct i3c_target_func_group, group);
+}
+
+static inline struct i3c_target_ctrl_group *to_i3c_target_ctrl_group(struct config_item *item)
+{
+ return container_of(to_config_group(item), struct i3c_target_ctrl_group, group);
+}
+
+static int i3c_target_ctrl_func_link(struct config_item *ctrl_cfg, struct config_item *func_cfg)
+{
+ struct i3c_target_func_group *func_group = to_i3c_target_func_group(func_cfg);
+ struct i3c_target_ctrl_group *ctrl_group = to_i3c_target_ctrl_group(ctrl_cfg);
+ struct i3c_target_ctrl *ctrl = ctrl_group->ctrl;
+ struct i3c_target_func *func = func_group->func;
+ int ret;
+
+ ret = i3c_target_ctrl_add_func(ctrl, func);
+ if (ret)
+ return ret;
+
+ ret = i3c_target_func_bind(func);
+ if (ret) {
+ i3c_target_ctrl_remove_func(ctrl, func);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void i3c_target_ctrl_func_unlink(struct config_item *ctrl_cfg, struct config_item *func_cfg)
+{
+ struct i3c_target_func_group *func_group = to_i3c_target_func_group(func_cfg->ci_parent);
+ struct i3c_target_ctrl_group *ctrl_group = to_i3c_target_ctrl_group(ctrl_cfg);
+ struct i3c_target_ctrl *ctrl = ctrl_group->ctrl;
+ struct i3c_target_func *func = func_group->func;
+
+ i3c_target_func_unbind(func);
+ i3c_target_ctrl_remove_func(ctrl, func);
+}
+
+static ssize_t i3c_target_ctrl_hotjoin_store(struct config_item *item, const char *page, size_t len)
+{
+ struct i3c_target_ctrl_group *ctrl_group = to_i3c_target_ctrl_group(item);
+ struct i3c_target_ctrl *ctrl;
+ int ret;
+
+ ctrl = ctrl_group->ctrl;
+
+ ret = i3c_target_ctrl_hotjoin(ctrl);
+ if (ret) {
+ dev_err(&ctrl->dev, "failed to hotjoin i3c target controller\n");
+ return -EINVAL;
+ }
+
+ return len;
+}
+
+static ssize_t i3c_target_ctrl_hotjoin_show(struct config_item *item, char *page)
+{
+ return sysfs_emit(page, "%d\n", 0);
+}
+
+CONFIGFS_ATTR(i3c_target_ctrl_, hotjoin);
+
+static struct configfs_item_operations i3c_target_ctrl_item_ops = {
+ .allow_link = i3c_target_ctrl_func_link,
+ .drop_link = i3c_target_ctrl_func_unlink,
+};
+
+static struct configfs_attribute *i3c_target_ctrl_attrs[] = {
+ &i3c_target_ctrl_attr_hotjoin,
+ NULL,
+};
+
+static const struct config_item_type i3c_target_ctrl_type = {
+ .ct_item_ops = &i3c_target_ctrl_item_ops,
+ .ct_attrs = i3c_target_ctrl_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
+/**
+ * i3c_target_cfs_add_ctrl_group() - add I3C target controller group
+ * @ctrl: I3C target controller device
+ *
+ * Return: Pointer to struct config_group
+ */
+struct config_group *i3c_target_cfs_add_ctrl_group(struct i3c_target_ctrl *ctrl)
+{
+ struct i3c_target_ctrl_group *ctrl_group;
+ struct config_group *group;
+ int ret;
+
+ ctrl_group = kzalloc(sizeof(*ctrl_group), GFP_KERNEL);
+ if (!ctrl_group) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ group = &ctrl_group->group;
+
+ config_group_init_type_name(group, dev_name(&ctrl->dev), &i3c_target_ctrl_type);
+ ret = configfs_register_group(controllers_group, group);
+ if (ret) {
+ pr_err("failed to register configfs group for %s\n", dev_name(&ctrl->dev));
+ goto err_register_group;
+ }
+
+ ctrl_group->ctrl = ctrl;
+
+ return group;
+
+err_register_group:
+ kfree(ctrl_group);
+
+err:
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL(i3c_target_cfs_add_ctrl_group);
+
+/**
+ * i3c_target_cfs_remove_ctrl_group() - remove I3C target controller group
+ * @group: the group to be removed
+ */
+void i3c_target_cfs_remove_ctrl_group(struct config_group *group)
+{
+ struct i3c_target_ctrl_group *ctrl_group;
+
+ if (!group)
+ return;
+
+ ctrl_group = container_of(group, struct i3c_target_ctrl_group, group);
+ i3c_target_ctrl_put(ctrl_group->ctrl);
+ configfs_unregister_group(&ctrl_group->group);
+ kfree(ctrl_group);
+}
+EXPORT_SYMBOL(i3c_target_cfs_remove_ctrl_group);
+
+#define I3C_SLAVE_ATTR_R(_name) \
+static ssize_t i3c_target_func_##_name##_show(struct config_item *item, char *page) \
+{ \
+ struct i3c_target_func *func = to_i3c_target_func_group(item)->func; \
+ return sysfs_emit(page, "0x%04x\n", func->_name); \
+}
+
+#define I3C_SLAVE_ATTR_W(_name, _u) \
+static ssize_t i3c_target_func_##_name##_store(struct config_item *item, \
+ const char *page, size_t len) \
+{ \
+ _u val; \
+ struct i3c_target_func *func = to_i3c_target_func_group(item)->func; \
+ if (kstrto##_u(page, 0, &val) < 0) \
+ return -EINVAL; \
+ func->_name = val; \
+ return len; \
+}
+
+I3C_SLAVE_ATTR_R(vendor_id);
+I3C_SLAVE_ATTR_W(vendor_id, u16);
+CONFIGFS_ATTR(i3c_target_func_, vendor_id);
+
+I3C_SLAVE_ATTR_R(vendor_info);
+I3C_SLAVE_ATTR_W(vendor_info, u16);
+CONFIGFS_ATTR(i3c_target_func_, vendor_info);
+
+I3C_SLAVE_ATTR_R(part_id);
+I3C_SLAVE_ATTR_W(part_id, u16);
+CONFIGFS_ATTR(i3c_target_func_, part_id);
+
+I3C_SLAVE_ATTR_R(instance_id);
+I3C_SLAVE_ATTR_W(instance_id, u8);
+CONFIGFS_ATTR(i3c_target_func_, instance_id);
+
+I3C_SLAVE_ATTR_R(ext_id);
+I3C_SLAVE_ATTR_W(ext_id, u16);
+CONFIGFS_ATTR(i3c_target_func_, ext_id);
+
+I3C_SLAVE_ATTR_R(max_write_len);
+I3C_SLAVE_ATTR_W(max_write_len, u16);
+CONFIGFS_ATTR(i3c_target_func_, max_write_len);
+
+I3C_SLAVE_ATTR_R(max_read_len);
+I3C_SLAVE_ATTR_W(max_read_len, u16);
+CONFIGFS_ATTR(i3c_target_func_, max_read_len);
+
+I3C_SLAVE_ATTR_R(bcr);
+I3C_SLAVE_ATTR_W(bcr, u8);
+CONFIGFS_ATTR(i3c_target_func_, bcr);
+
+I3C_SLAVE_ATTR_R(dcr);
+I3C_SLAVE_ATTR_W(dcr, u8);
+CONFIGFS_ATTR(i3c_target_func_, dcr);
+
+static struct configfs_attribute *i3c_target_func_attrs[] = {
+ &i3c_target_func_attr_vendor_id,
+ &i3c_target_func_attr_vendor_info,
+ &i3c_target_func_attr_part_id,
+ &i3c_target_func_attr_instance_id,
+ &i3c_target_func_attr_ext_id,
+ &i3c_target_func_attr_max_write_len,
+ &i3c_target_func_attr_max_read_len,
+ &i3c_target_func_attr_bcr,
+ &i3c_target_func_attr_dcr,
+ NULL,
+};
+
+static const struct config_item_type i3c_target_func_type = {
+ .ct_attrs = i3c_target_func_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
+static struct config_group *i3c_target_func_make(struct config_group *group, const char *name)
+{
+ struct i3c_target_func_group *func_group;
+ struct i3c_target_func *func;
+ int err;
+
+ func_group = kzalloc(sizeof(*func_group), GFP_KERNEL);
+ if (!func_group)
+ return ERR_PTR(-ENOMEM);
+
+ config_group_init_type_name(&func_group->group, name, &i3c_target_func_type);
+
+ func = i3c_target_func_create(group->cg_item.ci_name, name);
+ if (IS_ERR(func)) {
+ pr_err("failed to create i3c target function device\n");
+ err = -EINVAL;
+ goto free_group;
+ }
+
+ func->group = &func_group->group;
+
+ func_group->func = func;
+
+ return &func_group->group;
+
+free_group:
+ kfree(func_group);
+
+ return ERR_PTR(err);
+}
+
+static void i3c_target_func_drop(struct config_group *group, struct config_item *item)
+{
+ config_item_put(item);
+}
+
+static struct configfs_group_operations i3c_target_func_group_ops = {
+ .make_group = &i3c_target_func_make,
+ .drop_item = &i3c_target_func_drop,
+};
+
+static const struct config_item_type i3c_target_func_group_type = {
+ .ct_group_ops = &i3c_target_func_group_ops,
+ .ct_owner = THIS_MODULE,
+};
+
+/**
+ * i3c_target_cfs_add_func_group() - add I3C target function group
+ * @name: group name
+ *
+ * Return: Pointer to struct config_group
+ */
+struct config_group *i3c_target_cfs_add_func_group(const char *name)
+{
+ struct config_group *group;
+
+ group = configfs_register_default_group(functions_group, name,
+ &i3c_target_func_group_type);
+ if (IS_ERR(group))
+ pr_err("failed to register configfs group for %s function\n",
+ name);
+
+ return group;
+}
+EXPORT_SYMBOL(i3c_target_cfs_add_func_group);
+
+/**
+ * i3c_target_cfs_remove_func_group() - add I3C target function group
+ * @group: group to be removed
+ */
+void i3c_target_cfs_remove_func_group(struct config_group *group)
+{
+ if (IS_ERR_OR_NULL(group))
+ return;
+
+ configfs_unregister_default_group(group);
+}
+EXPORT_SYMBOL(i3c_target_cfs_remove_func_group);
+
+static const struct config_item_type i3c_target_controllers_type = {
+ .ct_owner = THIS_MODULE,
+};
+
+static const struct config_item_type i3c_target_functions_type = {
+ .ct_owner = THIS_MODULE,
+};
+
+static const struct config_item_type i3c_target_type = {
+ .ct_owner = THIS_MODULE,
+};
+
+static struct configfs_subsystem i3c_target_cfs_subsys = {
+ .su_group = {
+ .cg_item = {
+ .ci_namebuf = "i3c_target",
+ .ci_type = &i3c_target_type,
+ },
+ },
+ .su_mutex = __MUTEX_INITIALIZER(i3c_target_cfs_subsys.su_mutex),
+};
+
+static int __init i3c_target_cfs_init(void)
+{
+ int ret;
+ struct config_group *root = &i3c_target_cfs_subsys.su_group;
+
+ config_group_init(root);
+
+ ret = configfs_register_subsystem(&i3c_target_cfs_subsys);
+ if (ret) {
+ pr_err("Error %d while registering subsystem %s\n",
+ ret, root->cg_item.ci_namebuf);
+ goto err;
+ }
+
+ functions_group = configfs_register_default_group(root, "functions",
+ &i3c_target_functions_type);
+ if (IS_ERR(functions_group)) {
+ ret = PTR_ERR(functions_group);
+ pr_err("Error %d while registering functions group\n",
+ ret);
+ goto err_functions_group;
+ }
+
+ controllers_group =
+ configfs_register_default_group(root, "controllers",
+ &i3c_target_controllers_type);
+ if (IS_ERR(controllers_group)) {
+ ret = PTR_ERR(controllers_group);
+ pr_err("Error %d while registering controllers group\n",
+ ret);
+ goto err_controllers_group;
+ }
+
+ return 0;
+
+err_controllers_group:
+ configfs_unregister_default_group(functions_group);
+
+err_functions_group:
+ configfs_unregister_subsystem(&i3c_target_cfs_subsys);
+
+err:
+ return ret;
+}
+module_init(i3c_target_cfs_init);
+
+MODULE_DESCRIPTION("I3C FUNC CONFIGFS");
+MODULE_AUTHOR("Frank Li <[email protected]>");
diff --git a/drivers/i3c/target.c b/drivers/i3c/target.c
new file mode 100644
index 0000000000000..f0bcda2c4599f
--- /dev/null
+++ b/drivers/i3c/target.c
@@ -0,0 +1,453 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * configfs to configure the I3C Slave
+ *
+ * Copyright (C) 2023 NXP
+ * Author: Frank Li <[email protected]>
+ */
+
+#include <linux/configfs.h>
+#include <linux/module.h>
+#include <linux/i3c/target.h>
+#include <linux/idr.h>
+#include <linux/slab.h>
+
+static DEFINE_MUTEX(func_lock);
+static struct class *i3c_target_ctrl_class;
+
+static void i3c_target_func_dev_release(struct device *dev)
+{
+ struct i3c_target_func *func = to_i3c_target_func(dev);
+
+ kfree(func->name);
+ kfree(func);
+}
+
+static const struct device_type i3c_target_func_type = {
+ .release = i3c_target_func_dev_release,
+};
+
+static int i3c_target_func_match_driver(struct device *dev, struct device_driver *drv)
+{
+ return !strncmp(dev_name(dev), drv->name, strlen(drv->name));
+}
+
+static int i3c_target_func_device_probe(struct device *dev)
+{
+ struct i3c_target_func *func = to_i3c_target_func(dev);
+ struct i3c_target_func_driver *driver = to_i3c_target_func_driver(dev->driver);
+
+ if (!driver->probe)
+ return -ENODEV;
+
+ func->driver = driver;
+
+ return driver->probe(func);
+}
+
+static void i3c_target_func_device_remove(struct device *dev)
+{
+ struct i3c_target_func *func = to_i3c_target_func(dev);
+ struct i3c_target_func_driver *driver = to_i3c_target_func_driver(dev->driver);
+
+ if (driver->remove)
+ driver->remove(func);
+ func->driver = NULL;
+}
+
+static const struct bus_type i3c_target_func_bus_type = {
+ .name = "i3c_target_func",
+ .probe = i3c_target_func_device_probe,
+ .remove = i3c_target_func_device_remove,
+ .match = i3c_target_func_match_driver,
+};
+
+static void i3c_target_ctrl_release(struct device *dev)
+{
+ kfree(to_i3c_target_ctrl(dev));
+}
+
+static void devm_i3c_target_ctrl_release(struct device *dev, void *res)
+{
+ struct i3c_target_ctrl *ctrl = *(struct i3c_target_ctrl **)res;
+
+ i3c_target_ctrl_destroy(ctrl);
+}
+
+struct i3c_target_ctrl *
+__devm_i3c_target_ctrl_create(struct device *dev, const struct i3c_target_ctrl_ops *ops,
+ struct module *owner)
+{
+ struct i3c_target_ctrl **ptr, *ctrl;
+
+ ptr = devres_alloc(devm_i3c_target_ctrl_release, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr)
+ return ERR_PTR(-ENOMEM);
+
+ ctrl = __i3c_target_ctrl_create(dev, ops, owner);
+ if (!IS_ERR(ctrl)) {
+ *ptr = ctrl;
+ devres_add(dev, ptr);
+ } else {
+ devres_free(ptr);
+ }
+
+ return ctrl;
+}
+
+static int devm_i3c_target_ctrl_match(struct device *dev, void *res, void *match_data)
+{
+ struct i3c_target_ctrl **ptr = res;
+
+ return *ptr == match_data;
+}
+
+/**
+ * __i3c_target_ctrl_create() - create a new target controller device
+ * @dev: device that is creating the new target controller
+ * @ops: function pointers for performing target controller operations
+ * @owner: the owner of the module that creates the target controller device
+ *
+ * Return: Pointer to struct i3c_target_ctrl
+ */
+struct i3c_target_ctrl *
+__i3c_target_ctrl_create(struct device *dev, const struct i3c_target_ctrl_ops *ops,
+ struct module *owner)
+{
+ struct i3c_target_ctrl *ctrl;
+ int ret;
+
+ if (WARN_ON(!dev))
+ return ERR_PTR(-EINVAL);
+
+ ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL);
+ if (!ctrl)
+ return ERR_PTR(-ENOMEM);
+
+ device_initialize(&ctrl->dev);
+ ctrl->dev.class = i3c_target_ctrl_class;
+ ctrl->dev.parent = dev;
+ ctrl->dev.release = i3c_target_ctrl_release;
+ ctrl->ops = ops;
+
+ ret = dev_set_name(&ctrl->dev, "%s", dev_name(dev));
+ if (ret)
+ goto put_dev;
+
+ ret = device_add(&ctrl->dev);
+ if (ret)
+ goto put_dev;
+
+ ctrl->group = i3c_target_cfs_add_ctrl_group(ctrl);
+ if (!ctrl->group)
+ goto put_dev;
+
+ return ctrl;
+
+put_dev:
+ put_device(&ctrl->dev);
+ kfree(ctrl);
+
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(__i3c_target_ctrl_create);
+
+/**
+ * devm_i3c_target_ctrl_destroy() - destroy the target controller device
+ * @dev: device that hat has to be destroy
+ * @ctrl: the target controller device that has to be destroy
+ *
+ * Invoke to create a new target controller device and add it to i3c_target class. While at that, it
+ * also associates the device with the i3c_target using devres. On driver detach, release function
+ * is invoked on the devres data, then devres data is freed.
+ */
+void devm_i3c_target_ctrl_destroy(struct device *dev, struct i3c_target_ctrl *ctrl)
+{
+ int r;
+
+ r = devres_destroy(dev, devm_i3c_target_ctrl_release, devm_i3c_target_ctrl_match,
+ ctrl);
+ dev_WARN_ONCE(dev, r, "couldn't find I3C controller resource\n");
+}
+EXPORT_SYMBOL_GPL(devm_i3c_target_ctrl_destroy);
+
+/**
+ * i3c_target_ctrl_destroy() - destroy the target controller device
+ * @ctrl: the target controller device that has to be destroyed
+ *
+ * Invoke to destroy the I3C target device
+ */
+void i3c_target_ctrl_destroy(struct i3c_target_ctrl *ctrl)
+{
+ i3c_target_cfs_remove_ctrl_group(ctrl->group);
+ device_unregister(&ctrl->dev);
+}
+EXPORT_SYMBOL_GPL(i3c_target_ctrl_destroy);
+
+/**
+ * i3c_target_ctrl_add_func() - bind I3C target function to an target controller
+ * @ctrl: the controller device to which the target function should be added
+ * @func: the target function to be added
+ *
+ * An I3C target device can have only one functions.
+ */
+int i3c_target_ctrl_add_func(struct i3c_target_ctrl *ctrl, struct i3c_target_func *func)
+{
+ if (ctrl->func)
+ return -EBUSY;
+
+ ctrl->func = func;
+ func->ctrl = ctrl;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(i3c_target_ctrl_add_func);
+
+/**
+ * i3c_target_ctrl_remove_func() - unbind I3C target function to an target controller
+ * @ctrl: the controller device to which the target function should be removed
+ * @func: the target function to be removed
+ *
+ * An I3C target device can have only one functions.
+ */
+void i3c_target_ctrl_remove_func(struct i3c_target_ctrl *ctrl, struct i3c_target_func *func)
+{
+ ctrl->func = NULL;
+}
+EXPORT_SYMBOL_GPL(i3c_target_ctrl_remove_func);
+
+/**
+ * i3c_target_ctrl_get() - get the I3C target controller
+ * @name: device name of the target controller
+ *
+ * Invoke to get struct i3c_target_ctrl * corresponding to the device name of the
+ * target controller
+ */
+struct i3c_target_ctrl *i3c_target_ctrl_get(const char *name)
+{
+ int ret = -EINVAL;
+ struct i3c_target_ctrl *ctrl;
+ struct device *dev;
+ struct class_dev_iter iter;
+
+ class_dev_iter_init(&iter, i3c_target_ctrl_class, NULL, NULL);
+ while ((dev = class_dev_iter_next(&iter))) {
+ if (strcmp(name, dev_name(dev)))
+ continue;
+
+ ctrl = to_i3c_target_ctrl(dev);
+ if (!try_module_get(ctrl->ops->owner)) {
+ ret = -EINVAL;
+ goto err;
+ }
+
+ class_dev_iter_exit(&iter);
+ get_device(&ctrl->dev);
+ return ctrl;
+ }
+
+err:
+ class_dev_iter_exit(&iter);
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(i3c_target_ctrl_get);
+
+/**
+ * i3c_target_ctrl_put() - release the I3C endpoint controller
+ * @ctrl: target controller returned by pci_target_get()
+ *
+ * release the refcount the caller obtained by invoking i3c_target_ctrl_get()
+ */
+void i3c_target_ctrl_put(struct i3c_target_ctrl *ctrl)
+{
+ if (!ctrl || IS_ERR(ctrl))
+ return;
+
+ module_put(ctrl->ops->owner);
+ put_device(&ctrl->dev);
+}
+EXPORT_SYMBOL_GPL(i3c_target_ctrl_put);
+
+/**
+ * i3c_target_ctrl_hotjoin() - trigger device hotjoin
+ * @ctrl: target controller
+ *
+ * return: 0: success, others failure
+ */
+int i3c_target_ctrl_hotjoin(struct i3c_target_ctrl *ctrl)
+{
+ if (!ctrl || IS_ERR(ctrl))
+ return -EINVAL;
+
+ if (!ctrl->ops->hotjoin)
+ return -EINVAL;
+
+ return ctrl->ops->hotjoin(ctrl);
+}
+EXPORT_SYMBOL_GPL(i3c_target_ctrl_hotjoin);
+
+/**
+ * i3c_target_func_bind() - Notify the function driver that the function device has been bound to a
+ * controller device
+ * @func: the function device which has been bound to the controller device
+ *
+ * Invoke to notify the function driver that it has been bound to a controller device
+ */
+int i3c_target_func_bind(struct i3c_target_func *func)
+{
+ struct device *dev = &func->dev;
+ int ret;
+
+ if (!func->driver) {
+ dev_WARN(dev, "func device not bound to driver\n");
+ return -EINVAL;
+ }
+
+ if (!try_module_get(func->driver->owner))
+ return -EAGAIN;
+
+ mutex_lock(&func->lock);
+ ret = func->driver->ops->bind(func);
+ if (!ret)
+ func->is_bound = true;
+ mutex_unlock(&func->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(i3c_target_func_bind);
+
+/**
+ * i3c_target_func_unbind() - Notify the function driver that the binding between the function
+ * device and controller device has been lost.
+ * @func: the function device which has lost the binding with the controller device.
+ *
+ * Invoke to notify the function driver that the binding between the function device and controller
+ * device has been lost.
+ */
+void i3c_target_func_unbind(struct i3c_target_func *func)
+{
+ if (!func->driver) {
+ dev_WARN(&func->dev, "func device not bound to driver\n");
+ return;
+ }
+
+ mutex_lock(&func->lock);
+ if (func->is_bound)
+ func->driver->ops->unbind(func);
+ mutex_unlock(&func->lock);
+
+ module_put(func->driver->owner);
+}
+EXPORT_SYMBOL_GPL(i3c_target_func_unbind);
+
+/**
+ * i3c_target_func_create() - create a new I3C function device
+ * @drv_name: the driver name of the I3C function device.
+ * @name: the name of the function device.
+ *
+ * Invoke to create a new I3C function device by providing the name of the function device.
+ */
+struct i3c_target_func *i3c_target_func_create(const char *drv_name, const char *name)
+{
+ struct i3c_target_func *func;
+ struct device *dev;
+ int ret;
+
+ func = kzalloc(sizeof(*func), GFP_KERNEL);
+ if (!func)
+ return ERR_PTR(-ENOMEM);
+
+ dev = &func->dev;
+ device_initialize(dev);
+ dev->bus = &i3c_target_func_bus_type;
+ dev->type = &i3c_target_func_type;
+ mutex_init(&func->lock);
+
+ ret = dev_set_name(dev, "%s.%s", drv_name, name);
+ if (ret) {
+ put_device(dev);
+ return ERR_PTR(ret);
+ }
+
+ ret = device_add(dev);
+ if (ret) {
+ put_device(dev);
+ return ERR_PTR(ret);
+ }
+
+ return func;
+}
+EXPORT_SYMBOL_GPL(i3c_target_func_create);
+
+/**
+ * __i3c_target_func_register_driver() - register a new I3C function driver
+ * @driver: structure representing I3C function driver
+ * @owner: the owner of the module that registers the I3C function driver
+ *
+ * Invoke to register a new I3C function driver.
+ */
+int __i3c_target_func_register_driver(struct i3c_target_func_driver *driver, struct module *owner)
+{
+ int ret = -EEXIST;
+
+ if (!driver->ops)
+ return -EINVAL;
+
+ if (!driver->ops->bind || !driver->ops->unbind)
+ return -EINVAL;
+
+ driver->driver.bus = &i3c_target_func_bus_type;
+ driver->driver.owner = owner;
+
+ ret = driver_register(&driver->driver);
+ if (ret)
+ return ret;
+
+ i3c_target_cfs_add_func_group(driver->driver.name);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(__i3c_target_func_register_driver);
+
+/**
+ * i3c_target_func_unregister_driver() - unregister the I3C function driver
+ * @fd: the I3C function driver that has to be unregistered
+ *
+ * Invoke to unregister the I3C function driver.
+ */
+void i3c_target_func_unregister_driver(struct i3c_target_func_driver *fd)
+{
+ mutex_lock(&func_lock);
+ mutex_unlock(&func_lock);
+}
+EXPORT_SYMBOL_GPL(i3c_target_func_unregister_driver);
+
+static int __init i3c_target_init(void)
+{
+ int ret;
+
+ i3c_target_ctrl_class = class_create("i3c_target");
+ if (IS_ERR(i3c_target_ctrl_class)) {
+ pr_err("failed to create i3c target class --> %ld\n",
+ PTR_ERR(i3c_target_ctrl_class));
+ return PTR_ERR(i3c_target_ctrl_class);
+ }
+
+ ret = bus_register(&i3c_target_func_bus_type);
+ if (ret) {
+ class_destroy(i3c_target_ctrl_class);
+ pr_err("failed to register i3c target func bus --> %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+module_init(i3c_target_init);
+
+static void __exit i3c_target_exit(void)
+{
+ class_destroy(i3c_target_ctrl_class);
+ bus_unregister(&i3c_target_func_bus_type);
+}
+module_exit(i3c_target_exit);
+
diff --git a/include/linux/i3c/target.h b/include/linux/i3c/target.h
new file mode 100644
index 0000000000000..b0bf06685834c
--- /dev/null
+++ b/include/linux/i3c/target.h
@@ -0,0 +1,548 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2023 NXP.
+ *
+ * Author: Frank Li <[email protected]>
+ */
+
+#ifndef I3C_TARGET_H
+#define I3C_TARGET_H
+
+#include <linux/device.h>
+#include <linux/slab.h>
+
+struct i3c_target_func;
+struct i3c_target_ctrl;
+
+/**
+ * struct i3c_target_func_ops - set of function pointers for performing i3c target function
+ * operations
+ * @bind: ops to perform when a controller device has been bound to function device
+ * @unbind: ops to perform when a binding has been lost between a controller device and function
+ * device
+ */
+struct i3c_target_func_ops {
+ int (*bind)(struct i3c_target_func *func);
+ void (*unbind)(struct i3c_target_func *func);
+};
+
+/**
+ * struct i3c_target_func_driver - represents the I3C function driver
+ * @probe: ops to perform when a new function device has been bound to the function driver
+ * @remove: ops to perform when the binding between the function device and function driver is
+ * broken
+ * @name: I3C Function driver name
+ * @driver: I3C Function driver
+ * @ops: set of function pointers for performing function operations
+ * @owner: the owner of the module that registers the I3C function driver
+ * @epf_group: list of configfs group corresponding to the I3C function driver
+ */
+struct i3c_target_func_driver {
+ int (*probe)(struct i3c_target_func *func);
+ void (*remove)(struct i3c_target_func *func);
+
+ char *name;
+ struct device_driver driver;
+ struct i3c_target_func_ops *ops;
+ struct module *owner;
+};
+
+/**
+ * struct i3c_target_func - represents the I3C function device
+ * @dev: the I3C function device
+ * @name: the name of the I3C function device
+ * @driver: the function driver to which this function device is bound
+ * @group: configfs group associated with the EPF device
+ * @lock: mutex to protect i3c_target_func_ops
+ * @ctrl: binded I3C controller device
+ * @is_bound: indicates if bind notification to function driver has been invoked
+ * @vendor_id: vendor id
+ * @part_id: part id
+ * @instance_id: instance id
+ * @ext_id: ext id
+ * @vendor_info: vendor info
+ * @static_addr: static address for I2C. It is 0 for I3C.
+ * @max_write_len: maximum write length
+ * @max_read_len: maximum read length
+ * @bcr: bus characteristics register (BCR)
+ * @dcr: device characteristics register (DCR)
+ */
+struct i3c_target_func {
+ struct device dev;
+ char *name;
+ struct i3c_target_func_driver *driver;
+ struct config_group *group;
+ /* mutex to protect against concurrent access of i3c_target_func_ops */
+ struct mutex lock;
+ struct i3c_target_ctrl *ctrl;
+ bool is_bound;
+
+ u16 vendor_id;
+ u16 part_id;
+ u8 instance_id;
+ u16 ext_id;
+ u8 vendor_info;
+ u16 static_addr;
+ u16 max_write_len; //0 is hardware default max value
+ u16 max_read_len; //0 is hardware default max value
+ u8 bcr;
+ u8 dcr;
+};
+
+enum i3c_request_stat {
+ I3C_REQUEST_OKAY,
+ I3C_REQUEST_PARTIAL,
+ I3C_REQUEST_ERR,
+ I3C_REQUEST_CANCEL,
+};
+
+/**
+ * struct i3c_request - represents the an I3C transfer request
+ * @buf: data buffer
+ * @length: data length
+ * @complete: call back function when request finished or cancelled
+ * @context: general data for complete callback function
+ * @list: link list
+ * @status: transfer status
+ * @actual: how much actually transferred
+ * @ctrl: I3C target controller associate with this request
+ * @tx: transfer direction, 1: target to master, 0: master to target
+ */
+struct i3c_request {
+ void *buf;
+ unsigned int length;
+
+ void (*complete)(struct i3c_request *req);
+ void *context;
+ struct list_head list;
+
+ enum i3c_request_stat status;
+ unsigned int actual;
+ struct i3c_target_ctrl *ctrl;
+ bool tx;
+};
+
+/**
+ * struct i3c_target_ctrl_features - represents I3C target controller features.
+ * @tx_fifo_sz: tx hardware fifo size
+ * @rx_fifo_sz: rx hardware fifo size
+ */
+struct i3c_target_ctrl_features {
+ u32 tx_fifo_sz;
+ u32 rx_fifo_sz;
+};
+
+/**
+ * struct i3c_target_ctrl_ops - set of function pointers for performing controller operations
+ * @set_config: set I3C controller configuration
+ * @enable: enable I3C controller
+ * @disable: disable I3C controller
+ * @raise_ibi: raise IBI interrupt to master
+ * @alloc_request: allocate a i3c_request, optional, default use kmalloc
+ * @free_request: free a i3c_request, default use kfree
+ * @queue: queue an I3C transfer
+ * @dequeue: dequeue an I3C transfer
+ * @cancel_all_reqs: call all pending requests
+ * @fifo_status: current FIFO status
+ * @fifo_flush: flush hardware FIFO
+ * @hotjoin: raise hotjoin request to master
+ * @set_status_format1: set i3c status format1
+ * @get_status_format1: get i3c status format1
+ * @get_addr: get i3c dynmatic address
+ * @get_features: ops to get the features supported by the I3C target controller
+ * @owner: the module owner containing the ops
+ */
+struct i3c_target_ctrl_ops {
+ int (*set_config)(struct i3c_target_ctrl *ctrl, struct i3c_target_func *func);
+ int (*enable)(struct i3c_target_ctrl *ctrl);
+ int (*disable)(struct i3c_target_ctrl *ctrl);
+ int (*raise_ibi)(struct i3c_target_ctrl *ctrl, void *p, u8 size);
+
+ struct i3c_request *(*alloc_request)(struct i3c_target_ctrl *ctrl, gfp_t gfp_flags);
+ void (*free_request)(struct i3c_request *req);
+
+ int (*queue)(struct i3c_request *req, gfp_t gfp_flags);
+ int (*dequeue)(struct i3c_request *req);
+
+ void (*cancel_all_reqs)(struct i3c_target_ctrl *ctrl, bool tx);
+
+ int (*fifo_status)(struct i3c_target_ctrl *ctrl, bool tx);
+ void (*fifo_flush)(struct i3c_target_ctrl *ctrl, bool tx);
+ int (*hotjoin)(struct i3c_target_ctrl *ctrl);
+ int (*set_status_format1)(struct i3c_target_ctrl *ctrl, u16 status);
+ u16 (*get_status_format1)(struct i3c_target_ctrl *ctrl);
+ u8 (*get_addr)(struct i3c_target_ctrl *ctrl);
+ const struct i3c_target_ctrl_features *(*get_features)(struct i3c_target_ctrl *ctrl);
+ struct module *owner;
+};
+
+/**
+ * struct i3c_target_ctrl - represents the I3C target device
+ * @dev: I3C target device
+ * @ops: function pointers for performing endpoint operations
+ * @func: target functions present in this controller device
+ * @group: configfs group representing the I3C controller device
+ */
+struct i3c_target_ctrl {
+ struct device dev;
+ const struct i3c_target_ctrl_ops *ops;
+ struct i3c_target_func *func;
+ struct config_group *group;
+};
+
+/**
+ * i3c_target_ctrl_raise_ibi() - Raise IBI to master
+ * @ctrl: I3C target controller
+ * @p: optional data for IBI
+ * @size: size of optional data
+ *
+ * Returns: Zero for success, or an error code in case of failure
+ */
+static inline int i3c_target_ctrl_raise_ibi(struct i3c_target_ctrl *ctrl, void *p, u8 size)
+{
+ if (ctrl && ctrl->ops && ctrl->ops->raise_ibi)
+ return ctrl->ops->raise_ibi(ctrl, p, size);
+
+ return -EINVAL;
+}
+
+/**
+ * i3c_target_ctrl_cancel_all_reqs() - Cancel all pending request
+ * @ctrl: I3C target controller
+ * @tx: Transfer diretion queue
+ */
+static inline void i3c_target_ctrl_cancel_all_reqs(struct i3c_target_ctrl *ctrl, bool tx)
+{
+ if (ctrl && ctrl->ops && ctrl->ops->cancel_all_reqs)
+ ctrl->ops->cancel_all_reqs(ctrl, tx);
+}
+
+/**
+ * i3c_target_ctrl_set_config() - Set controller configuration
+ * @ctrl: I3C target controller device
+ * @func: Function device
+ *
+ * Returns: Zero for success, or an error code in case of failure
+ */
+static inline int
+i3c_target_ctrl_set_config(struct i3c_target_ctrl *ctrl, struct i3c_target_func *func)
+{
+ if (ctrl && ctrl->ops && ctrl->ops->set_config)
+ return ctrl->ops->set_config(ctrl, func);
+
+ return -EINVAL;
+}
+
+/**
+ * i3c_target_ctrl_enable() - Enable I3C controller
+ * @ctrl: I3C target controller device
+ *
+ * Returns: Zero for success, or an error code in case of failure
+ */
+static inline int
+i3c_target_ctrl_enable(struct i3c_target_ctrl *ctrl)
+{
+ if (ctrl && ctrl->ops && ctrl->ops->enable)
+ return ctrl->ops->enable(ctrl);
+
+ return -EINVAL;
+}
+
+/**
+ * i3c_target_ctrl_disable() - Disable I3C controller
+ * @ctrl: I3C target controller device
+ *
+ * Returns: Zero for success, or an error code in case of failure
+ */
+static inline int
+i3c_target_ctrl_disable(struct i3c_target_ctrl *ctrl)
+{
+ if (ctrl && ctrl->ops && ctrl->ops->disable)
+ return ctrl->ops->disable(ctrl);
+
+ return -EINVAL;
+}
+
+/**
+ * i3c_target_ctrl_alloc_request() - Alloc an I3C transfer
+ * @ctrl: I3C target controller device
+ * @gfp_flags: additional gfp flags used when allocating the buffers
+ *
+ * Returns: Zero for success, or an error code in case of failure
+ */
+static inline struct i3c_request *
+i3c_target_ctrl_alloc_request(struct i3c_target_ctrl *ctrl, gfp_t gfp_flags)
+{
+ struct i3c_request *req = NULL;
+
+ if (ctrl && ctrl->ops && ctrl->ops->alloc_request)
+ req = ctrl->ops->alloc_request(ctrl, gfp_flags);
+ else
+ req = kzalloc(sizeof(*req), gfp_flags);
+
+ if (req)
+ req->ctrl = ctrl;
+
+ return req;
+}
+
+/**
+ * i3c_target_ctrl_free_request() - Free an I3C transfer
+ * @req: I3C transfer request
+ *
+ * Returns: Zero for success, or an error code in case of failure
+ */
+static inline void
+i3c_target_ctrl_free_request(struct i3c_request *req)
+{
+ struct i3c_target_ctrl *ctrl;
+
+ if (!req)
+ return;
+
+ ctrl = req->ctrl;
+ if (ctrl && ctrl->ops && ctrl->ops->free_request)
+ ctrl->ops->free_request(req);
+ else
+ kfree(req);
+}
+
+/**
+ * i3c_target_ctrl_queue() - Queue an I3C transfer
+ * @req: I3C transfer request
+ * @gfp_flags: additional gfp flags used when allocating the buffers
+ *
+ * Returns: Zero for success, or an error code in case of failure
+ */
+static inline int
+i3c_target_ctrl_queue(struct i3c_request *req, gfp_t gfp_flags)
+{
+ struct i3c_target_ctrl *ctrl;
+ int ret = -EINVAL;
+
+ if (!req)
+ return -EINVAL;
+
+ ctrl = req->ctrl;
+
+ req->actual = 0;
+ req->status = 0;
+ if (ctrl && ctrl->ops && ctrl->ops->queue)
+ ret = ctrl->ops->queue(req, gfp_flags);
+
+ return ret;
+}
+
+/**
+ * i3c_target_ctrl_dequeue() - Dequeue an I3C transfer
+ * @req: I3C transfer request
+ *
+ * Returns: Zero for success, or an error code in case of failure
+ */
+static inline int
+i3c_target_ctrl_dequeue(struct i3c_request *req)
+{
+ struct i3c_target_ctrl *ctrl;
+ int ret = -EINVAL;
+
+ if (!req)
+ return -EINVAL;
+
+ ctrl = req->ctrl;
+ if (ctrl && ctrl->ops && ctrl->ops->dequeue)
+ ret = ctrl->ops->dequeue(req);
+
+ return ret;
+}
+
+/**
+ * i3c_target_ctrl_fifo_status() - Get controller FIFO status
+ * @ctrl: I3C target controller device
+ * @tx: 1: Target to master, 0: master to target
+ *
+ * Returns: How much data in FIFO
+ */
+static inline int
+i3c_target_ctrl_fifo_status(struct i3c_target_ctrl *ctrl, bool tx)
+{
+ if (ctrl && ctrl->ops && ctrl->ops->fifo_status)
+ return ctrl->ops->fifo_status(ctrl, tx);
+
+ return 0;
+}
+
+/**
+ * i3c_target_ctrl_fifo_flush() - Flush controller FIFO
+ * @ctrl: I3C target controller device
+ * @tx: 1: Target to master, 0: master to target
+ *
+ */
+static inline void
+i3c_target_ctrl_fifo_flush(struct i3c_target_ctrl *ctrl, bool tx)
+{
+ if (ctrl && ctrl->ops && ctrl->ops->fifo_flush)
+ return ctrl->ops->fifo_flush(ctrl, tx);
+}
+
+/**
+ * i3c_target_ctrl_get_features() - Get controller supported features
+ * @ctrl: I3C target controller device
+ *
+ * Returns: The pointer to struct i3c_target_ctrl_features
+ */
+static inline const struct i3c_target_ctrl_features*
+i3c_target_ctrl_get_features(struct i3c_target_ctrl *ctrl)
+{
+ if (ctrl && ctrl->ops && ctrl->ops->get_features)
+ return ctrl->ops->get_features(ctrl);
+
+ return NULL;
+}
+
+/**
+ * i3c_target_ctrl_set_status_format1() - Set controller supported features
+ * @ctrl: I3C target controller device
+ * @status: I3C GETSTATUS format1
+ *
+ * Returns: Zero for success, or an error code in case of failure
+ */
+static inline int
+i3c_target_ctrl_set_status_format1(struct i3c_target_ctrl *ctrl, u16 status)
+{
+ if (ctrl && ctrl->ops && ctrl->ops->set_status_format1)
+ return ctrl->ops->set_status_format1(ctrl, status);
+
+ return -EINVAL;
+}
+
+/**
+ * i3c_target_ctrl_get_status_format1() - Get controller supported features
+ * @ctrl: I3C target controller device
+ *
+ * Return: I3C GETSTATUS format1
+ */
+static inline u16
+i3c_target_ctrl_get_status_format1(struct i3c_target_ctrl *ctrl)
+{
+ if (ctrl && ctrl->ops && ctrl->ops->get_status_format1)
+ return ctrl->ops->get_status_format1(ctrl);
+
+ return 0;
+}
+
+/**
+ * i3c_target_ctrl_get_addr() - Get controller address
+ * @ctrl: I3C target controller device
+ *
+ * Return: address
+ */
+static inline u8 i3c_target_ctrl_get_addr(struct i3c_target_ctrl *ctrl)
+{
+ if (ctrl && ctrl->ops && ctrl->ops->get_addr)
+ return ctrl->ops->get_addr(ctrl);
+
+ return 0;
+}
+
+#define to_i3c_target_ctrl(device) container_of((device), struct i3c_target_ctrl, dev)
+
+#define to_i3c_target_func(func_dev) container_of((func_dev), struct i3c_target_func, dev)
+#define to_i3c_target_func_driver(drv) (container_of((drv), struct i3c_target_func_driver, driver))
+
+#define i3c_target_ctrl_create(dev, ops) \
+ __i3c_target_ctrl_create((dev), (ops), THIS_MODULE)
+#define devm_i3c_target_ctrl_create(dev, ops) \
+ __devm_i3c_target_ctrl_create((dev), (ops), THIS_MODULE)
+
+#ifdef CONFIG_I3C_TARGET
+struct i3c_target_ctrl *
+__devm_i3c_target_ctrl_create(struct device *dev, const struct i3c_target_ctrl_ops *ops,
+ struct module *owner);
+struct i3c_target_ctrl *
+__i3c_target_ctrl_create(struct device *dev, const struct i3c_target_ctrl_ops *ops,
+ struct module *owner);
+
+void devm_i3c_target_ctrl_destroy(struct device *dev, struct i3c_target_ctrl *epc);
+void i3c_target_ctrl_destroy(struct i3c_target_ctrl *epc);
+
+int i3c_target_ctrl_add_func(struct i3c_target_ctrl *ctrl, struct i3c_target_func *func);
+void i3c_target_ctrl_remove_func(struct i3c_target_ctrl *ctrl, struct i3c_target_func *func);
+int i3c_target_ctrl_hotjoin(struct i3c_target_ctrl *ctrl);
+
+struct i3c_target_ctrl *i3c_target_ctrl_get(const char *name);
+void i3c_target_ctrl_put(struct i3c_target_ctrl *ctrl);
+
+int i3c_target_func_bind(struct i3c_target_func *func);
+void i3c_target_func_unbind(struct i3c_target_func *func);
+struct i3c_target_func *i3c_target_func_create(const char *drv_name, const char *name);
+
+#define i3c_target_func_register_driver(drv) \
+ __i3c_target_func_register_driver(drv, THIS_MODULE)
+
+int __i3c_target_func_register_driver(struct i3c_target_func_driver *drv, struct module *owner);
+void i3c_target_func_unregister_driver(struct i3c_target_func_driver *drv);
+#else
+static inline struct i3c_target_ctrl *
+__devm_i3c_target_ctrl_create(struct device *dev, const struct i3c_target_ctrl_ops *ops,
+ struct module *owner)
+{
+ return NULL;
+}
+
+static inline struct i3c_target_ctrl *
+__i3c_target_ctrl_create(struct device *dev, const struct i3c_target_ctrl_ops *ops,
+ struct module *owner)
+{
+ return NULL;
+}
+#endif
+
+#ifdef CONFIG_I3C_TARGET_CONFIGFS
+struct config_group *i3c_target_cfs_add_ctrl_group(struct i3c_target_ctrl *ctrl);
+void i3c_target_cfs_remove_ctrl_group(struct config_group *group);
+struct config_group *i3c_target_cfs_add_func_group(const char *name);
+void i3c_target_cfs_remove_func_group(struct config_group *group);
+#else
+static inline struct config_group *i3c_target_cfs_add_ctrl_group(struct i3c_target_ctrl *ctrl)
+{
+ return NULL;
+}
+
+static inline void i3c_target_cfs_remove_ctrl_group(struct config_group *group)
+{
+}
+
+static inline struct config_group *i3c_target_cfs_add_func_group(const char *name)
+{
+ return NULL;
+}
+
+static inline void i3c_target_cfs_remove_func_group(struct config_group *group)
+{
+}
+#endif
+
+#define DECLARE_I3C_TARGET_FUNC(_name, _probe, _remove, _ops) \
+ static struct i3c_target_func_driver _name ## i3c_func = { \
+ .driver.name = __stringify(_name), \
+ .owner = THIS_MODULE, \
+ .probe = _probe, \
+ .remove = _remove, \
+ .ops = _ops \
+ }; \
+ MODULE_ALIAS("i3cfunc:" __stringify(_name))
+
+#define DECLARE_I3C_TARGET_INIT(_name, _probe, _remove, _ops) \
+ DECLARE_I3C_TARGET_FUNC(_name, _probe, _remove, _ops); \
+ static int __init _name ## mod_init(void) \
+ { \
+ return i3c_target_func_register_driver(&_name ## i3c_func); \
+ } \
+ static void __exit _name ## mod_exit(void) \
+ { \
+ i3c_target_func_unregister_driver(&_name ## i3c_func); \
+ } \
+ module_init(_name ## mod_init); \
+ module_exit(_name ## mod_exit)
+
+#endif
--
2.34.1
Add I3C target mode and tty over i3c func driver document.
Signed-off-by: Frank Li <[email protected]>
---
Documentation/driver-api/i3c/index.rst | 1 +
.../driver-api/i3c/target/i3c-target-cfs.rst | 109 ++++++++++
.../driver-api/i3c/target/i3c-target.rst | 189 ++++++++++++++++++
.../driver-api/i3c/target/i3c-tty-howto.rst | 109 ++++++++++
Documentation/driver-api/i3c/target/index.rst | 13 ++
5 files changed, 421 insertions(+)
create mode 100644 Documentation/driver-api/i3c/target/i3c-target-cfs.rst
create mode 100644 Documentation/driver-api/i3c/target/i3c-target.rst
create mode 100644 Documentation/driver-api/i3c/target/i3c-tty-howto.rst
create mode 100644 Documentation/driver-api/i3c/target/index.rst
diff --git a/Documentation/driver-api/i3c/index.rst b/Documentation/driver-api/i3c/index.rst
index 783d6dad054b6..345a43c9f61b0 100644
--- a/Documentation/driver-api/i3c/index.rst
+++ b/Documentation/driver-api/i3c/index.rst
@@ -9,3 +9,4 @@ I3C subsystem
protocol
device-driver-api
master-driver-api
+ target/index
diff --git a/Documentation/driver-api/i3c/target/i3c-target-cfs.rst b/Documentation/driver-api/i3c/target/i3c-target-cfs.rst
new file mode 100644
index 0000000000000..1fcf829dc4ae2
--- /dev/null
+++ b/Documentation/driver-api/i3c/target/i3c-target-cfs.rst
@@ -0,0 +1,109 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+=======================================
+Configuring I3C Target Using CONFIGFS
+=======================================
+
+:Author: Frank Li <[email protected]>
+
+The I3C Target Core exposes configfs entry (i3c_target) to configure the I3C
+target function and to bind the target function with the target controller.
+(For introducing other mechanisms to configure the I3C Target Function refer to
+[1]).
+
+Mounting configfs
+=================
+
+The I3C Target Core layer creates i3c_target directory in the mounted configfs
+directory. configfs can be mounted using the following command::
+
+ mount -t configfs none /sys/kernel/config
+
+Directory Structure
+===================
+
+The i3c_target configfs has two directories at its root: controllers and
+functions. Every Controller device present in the system will have an entry in
+the *controllers* directory and every Function driver present in the system will
+have an entry in the *functions* directory.
+::
+
+ /sys/kernel/config/i3c_target/
+ .. controllers/
+ .. functions/
+
+Creating Function Device
+===================
+
+Every registered Function driver will be listed in controllers directory. The
+entries corresponding to Function driver will be created by the Function core.
+::
+
+ /sys/kernel/config/i3c_target/functions/
+ .. <Function Driver1>/
+ ... <Function Device 11>/
+ ... <Function Device 21>/
+ ... <Function Device 31>/
+ .. <Function Driver2>/
+ ... <Function Device 12>/
+ ... <Function Device 22>/
+
+In order to create a <Function device> of the type probed by <Function Driver>,
+the user has to create a directory inside <Function DriverN>.
+
+Every <Function device> directory consists of the following entries that can be
+used to configure the standard configuration header of the target function.
+(These entries are created by the framework when any new <Function Device> is
+created)
+::
+
+ .. <Function Driver1>/
+ ... <Function Device 11>/
+ ... vendor_id
+ ... part_id
+ ... bcr
+ ... dcr
+ ... ext_id
+ ... instance_id
+ ... max_read_len
+ ... max_write_len
+ ... vendor_info
+
+Controller Device
+==========
+
+Every registered Controller device will be listed in controllers directory. The
+entries corresponding to Controller device will be created by the Controller
+core.
+::
+
+ /sys/kernel/config/i3c_target/controllers/
+ .. <Controller Device1>/
+ ... <Symlink Function Device11>/
+ .. <Controller Device2>/
+ ... <Symlink Function Device21>/
+
+The <Controller Device> directory will have a list of symbolic links to
+<Function Device>. These symbolic links should be created by the user to
+represent the functions present in the target device. Only <Function Device>
+that represents a physical function can be linked to a Controller device.
+
+::
+
+ | controllers/
+ | <Directory: Controller name>/
+ | <Symbolic Link: Function>
+ | functions/
+ | <Directory: Function driver>/
+ | <Directory: Function device>/
+ | vendor_id
+ | part_id
+ | bcr
+ | dcr
+ | ext_id
+ | instance_id
+ | max_read_len
+ | max_write_len
+ | vendor_info
+
+[1] Documentation/I3C/target/pci-target.rst
diff --git a/Documentation/driver-api/i3c/target/i3c-target.rst b/Documentation/driver-api/i3c/target/i3c-target.rst
new file mode 100644
index 0000000000000..09ae26b1f311a
--- /dev/null
+++ b/Documentation/driver-api/i3c/target/i3c-target.rst
@@ -0,0 +1,189 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+:Author: Frank Li <[email protected]>
+
+This document is a guide to use the I3C Target Framework in order to create
+target controller driver, target function driver, and using configfs interface
+to bind the function driver to the controller driver.
+
+Introduction
+============
+
+Linux has a comprehensive I3C subsystem to support I3C controllers that
+operates in master mode. The subsystem has capability to scan I3C bus,assign
+i3c device address, load I3C driver (based on Manufacturer ID, part ID),
+support other services like hot-join, In-Band Interrupt(IBI).
+
+However the I3C controller IP integrated in some SoCs is capable of operating
+either in Master mode or Target mode. I3C Target Framework will add target mode
+support in Linux. This will help to run Linux in an target system which can
+have a wide variety of use cases from testing or validation, co-processor
+accelerator, etc.
+
+I3C Target Core
+=================
+
+The I3C Target Core layer comprises 3 components: the Target Controller
+library, the Target Function library, and the configfs layer to bind the target
+function with the target controller.
+
+I3C Target Controller Library
+------------------------------------
+
+The Controller library provides APIs to be used by the controller that can
+operate in target mode. It also provides APIs to be used by function
+driver/library in order to implement a particular target function.
+
+APIs for the I3C Target controller Driver
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This section lists the APIs that the I3C Target core provides to be used by the
+I3C controller driver.
+
+* devm_i3c_target_ctrl_create()/i3c_target_ctrl_create()
+
+ The I3C controller driver should implement the following ops:
+
+ * set_config: ops to set i3c configuration
+ * enable: ops to enable controller
+ * disable: ops to disable controller
+ * raise_ibi: ops to raise IBI to master controller
+ * alloc_request: ops to alloc a transfer request
+ * free_request: ops to free a transfer request
+ * queue: ops to queue a request to transfer queue
+ * dequeue: ops to dequeue a request from transfer queue
+ * cancel_all_reqs: ops to cancel all request from transfer queue
+ * fifo_status: ops to get fifo status
+ * fifo_flush: ops to flush hardware fifo
+ * get_features: ops to get controller supported features
+
+ The I3C controller driver can then create a new Controller device by
+ invoking devm_i3c_target_ctrl_create()/i3c_target_ctrl_create().
+
+* devm_i3c_target_ctrl_destroy()/i3c_target_ctrl_destroy()
+
+ The I3C controller driver can destroy the Controller device created by
+ either devm_i3c_target_ctrl_create() or i3c_target_ctrl_create() using
+ devm_i3c_target_ctrl_destroy() or i3c_target_ctrl_destroy().
+
+I3C Target Controller APIs for the I3C Target Function Driver
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This section lists the APIs that the I3C Target core provides to be used by the
+I3C target function driver.
+
+* i3c_target_ctrl_set_config()
+
+ The I3C target function driver should use i3c_target_ctrl_set_config() to
+ write i3c configuration to the target controller.
+
+* i3c_target_ctrl_enable()/i3c_target_ctrl_disable()
+
+ The I3C target function driver should use i3c_target_ctrl_enable()/
+ i3c_target_ctrl_disable() to enable/disable i3c target controller.
+
+* i3c_target_ctrl_alloc_request()/i3c_target_ctrl_free_request()
+
+ The I3C target function driver should usei3c_target_ctrl_alloc_request() /
+ i3c_target_ctrl_free_request() to alloc/free a i3c request.
+
+* i3c_target_ctrl_raise_ibi()
+
+ The I3C target function driver should use i3c_target_ctrl_raise_ibi() to
+ raise IBI.
+
+* i3c_target_ctrl_queue()/i3c_target_ctrl_dequeue()
+
+ The I3C target function driver should use i3c_target_ctrl_queue()/
+ i3c_target_ctrl_dequeue(), to queue/dequeue I3C transfer to/from transfer
+ queue.
+
+* i3c_target_ctrl_get_features()
+
+ The I3C target function driver should use i3c_target_ctrl_get_features() to
+ get I3C target controller supported features.
+
+Other I3C Target Controller APIs
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+There are other APIs provided by the Controller library. These are used for
+binding the I3C Target Function device with Controlller device. i3c-cfs.c can
+be used as reference for using these APIs.
+
+* i3c_target_ctrl_get()
+
+ Get a reference to the I3C target controller based on the device name of
+ the controller.
+
+* i3c_target_ctrl_put()
+
+ Release the reference to the I3C target controller obtained using
+ i3c_target_ctrl_get()
+
+* i3c_target_ctrl_add_func()
+
+ Add a I3C target function to a I3C target controller.
+
+* i3c_target_ctrl_remove_func()
+
+ Remove the I3C target function from I3C target controller.
+
+I3C Target Function Library
+----------------------------------
+
+The I3C Target Function library provides APIs to be used by the function driver
+and the Controller library to provide target mode functionality.
+
+I3C Target Function APIs for the I3C Target Function Driver
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This section lists the APIs that the I3C Target core provides to be used
+by the I3C target function driver.
+
+* i3c_target_func_register_driver()
+
+ The I3C Target Function driver should implement the following ops:
+ * bind: ops to perform when a Controller device has been bound to
+ Function device
+ * unbind: ops to perform when a binding has been lost between a
+ Controller device and Function device
+
+ The I3C Function driver can then register the I3C Function driver by using
+ i3c_target_func_register_driver().
+
+* i3c_target_func_unregister_driver()
+
+ The I3C Function driver can unregister the I3C Function driver by using
+ i3c_epf_unregister_driver().
+
+APIs for the I3C Target Controller Library
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This section lists the APIs that the I3C Target core provides to be used by the
+I3C target controller library.
+
+Other I3C Target APIs
+~~~~~~~~~~~~~~~~~~~~
+
+There are other APIs provided by the Function library. These are used to notify
+the function driver when the Function device is bound to the EPC device.
+i3c-cfs.c can be used as reference for using these APIs.
+
+* i3c_target_func_create()
+
+ Create a new I3C Function device by passing the name of the I3C EPF device.
+ This name will be used to bind the Function device to a Function driver.
+
+* i3c_target_func_destroy()
+
+ Destroy the created I3C Function device.
+
+* i3c_target_func_bind()
+
+ i3c_target_func_bind() should be invoked when the EPF device has been bound
+ to a Controller device.
+
+* i3c_target_func_unbind()
+
+ i3c_target_func_unbind() should be invoked when the binding between EPC
+ device and function device is lost.
diff --git a/Documentation/driver-api/i3c/target/i3c-tty-howto.rst b/Documentation/driver-api/i3c/target/i3c-tty-howto.rst
new file mode 100644
index 0000000000000..43a129b18e938
--- /dev/null
+++ b/Documentation/driver-api/i3c/target/i3c-tty-howto.rst
@@ -0,0 +1,109 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+===================
+I3C TTY User Guide
+===================
+
+:Author: Frank Li <[email protected]>
+
+This document is a guide to help users use i3c-target-tty function driver and
+i3ctty master driver for testing I3C. The list of steps to be followed in the
+master side and target side is given below.
+
+Endpoint Device
+===============
+
+Endpoint Controller Devices
+---------------------------
+
+To find the list of target controller devices in the system::
+
+ # ls /sys/class/i3c_target/
+ 44330000.i3c-target
+
+If CONFIG_I3C_SLAVE_CONFIGFS is enabled::
+
+ # ls /sys/kernel/config/i3c_target/controllers/
+ 44330000.i3c-target
+
+
+Endpoint Function Drivers
+-------------------------
+
+To find the list of target function drivers in the system::
+
+ # ls /sys/bus/i3c_target_func/drivers
+ tty
+
+If CONFIG_I3C_SLAVE_CONFIGFS is enabled::
+
+ # ls /sys/kernel/config/i3c_target/functions
+ tty
+
+
+Creating i3c-target-tty Device
+----------------------------
+
+I3C target function device can be created using the configfs. To create
+i3c-target-tty device, the following commands can be used::
+
+ # mount -t configfs none /sys/kernel/config
+ # cd /sys/kernel/config/i3c_target/
+ # mkdir functions/tty/func1
+
+The "mkdir func1" above creates the i3c-target-tty function device that will
+be probed by i3c tty driver.
+
+The I3C target framework populates the directory with the following
+configurable fields::
+
+ # ls functions/tty/func1
+ bcr dcr ext_id instance_id max_read_len max_write_len
+ part_id vendor_id vendor_info
+
+The I3C target function driver populates these entries with default values when
+the device is bound to the driver. The i3c-target-tty driver populates vendorid
+with 0xffff and interrupt_pin with 0x0001::
+
+ # cat functions/tty/func1/vendor_id
+ 0x0
+
+Configuring i3c-target-tty Device
+-------------------------------
+
+The user can configure the i3c-target-tty device using configfs entry. In order
+to change the vendorid, the following commands can be used::
+
+ # echo 0x011b > functions/tty/func1/vendor_id
+ # echo 0x1000 > functions/tty/func1/part_id
+ # echo 0x6 > functions/tty/t/bcr
+
+Binding i3c-target-tty Device to target Controller
+------------------------------------------------
+
+In order for the target function device to be useful, it has to be bound to a
+I3C target controller driver. Use the configfs to bind the function device to
+one of the controller driver present in the system::
+
+ # ln -s functions/tty/func1 controllers/44330000.i3c-target/
+
+I3C Master Device
+================
+
+Check I3C tty device is probed
+
+ # ls /sys/bus/i3c/devices/0-23610000000
+ 0-23610000000:0 bcr dcr driver dynamic_address hdrcap
+ modalias pid power subsystem tty uevent
+
+Using Target TTY function Device
+-----------------------------------
+
+Host side:
+ cat /dev/ttyI3C0
+Target side
+ echo abc >/dev/ttyI3C0
+
+You will see "abc" show at console.
+
+You can use other tty tool to test I3C target tty device.
diff --git a/Documentation/driver-api/i3c/target/index.rst b/Documentation/driver-api/i3c/target/index.rst
new file mode 100644
index 0000000000000..56eabfae83aa4
--- /dev/null
+++ b/Documentation/driver-api/i3c/target/index.rst
@@ -0,0 +1,13 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+======================
+I3C Target Framework
+======================
+
+.. toctree::
+ :maxdepth: 2
+
+ i3c-target
+ i3c-target-cfs
+ i3c-tty-howto
+
--
2.34.1
SVC i3c is a dual role controller. Move probe() into svc-i3c-main.c. This
prepares to support target probe depending on dts "mode" settings.
Signed-off-by: Frank Li <[email protected]>
---
Notes:
Change from v6 to v7
- using /* SPDX-License-Identifier */ for svc-i3c.h
Change from v5 to v6
- none
Change from v4 to v5
- add new line at end of file
New file from v4
drivers/i3c/master/Makefile | 3 +-
drivers/i3c/master/svc-i3c-main.c | 53 +++++++++++++++++++++++++++++
drivers/i3c/master/svc-i3c-master.c | 34 ++++--------------
drivers/i3c/master/svc-i3c.h | 12 +++++++
4 files changed, 73 insertions(+), 29 deletions(-)
create mode 100644 drivers/i3c/master/svc-i3c-main.c
create mode 100644 drivers/i3c/master/svc-i3c.h
diff --git a/drivers/i3c/master/Makefile b/drivers/i3c/master/Makefile
index 3e97960160bc8..484cb81f45821 100644
--- a/drivers/i3c/master/Makefile
+++ b/drivers/i3c/master/Makefile
@@ -2,5 +2,6 @@
obj-$(CONFIG_CDNS_I3C_MASTER) += i3c-master-cdns.o
obj-$(CONFIG_DW_I3C_MASTER) += dw-i3c-master.o
obj-$(CONFIG_AST2600_I3C_MASTER) += ast2600-i3c-master.o
-obj-$(CONFIG_SVC_I3C_MASTER) += svc-i3c-master.o
+svc-i3c-objs += svc-i3c-main.o svc-i3c-master.o
+obj-$(CONFIG_SVC_I3C_MASTER) += svc-i3c.o
obj-$(CONFIG_MIPI_I3C_HCI) += mipi-i3c-hci/
diff --git a/drivers/i3c/master/svc-i3c-main.c b/drivers/i3c/master/svc-i3c-main.c
new file mode 100644
index 0000000000000..6be6a576cdf7a
--- /dev/null
+++ b/drivers/i3c/master/svc-i3c-main.c
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+
+#include "svc-i3c.h"
+
+static int svc_i3c_probe(struct platform_device *pdev)
+{
+ return svc_i3c_master_probe(pdev);
+}
+
+static void svc_i3c_remove(struct platform_device *pdev)
+{
+ svc_i3c_master_remove(pdev);
+}
+
+static int __maybe_unused svc_i3c_runtime_suspend(struct device *dev)
+{
+ return svc_i3c_master_runtime_suspend(dev);
+}
+
+static int __maybe_unused svc_i3c_runtime_resume(struct device *dev)
+{
+ return svc_i3c_master_runtime_resume(dev);
+}
+
+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-v1"},
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, svc_i3c_master_of_match_tbl);
+
+static struct platform_driver svc_i3c_master = {
+ .probe = svc_i3c_probe,
+ .remove_new = svc_i3c_remove,
+ .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/master/svc-i3c-master.c b/drivers/i3c/master/svc-i3c-master.c
index 5ee4db68988e2..4dfe85ab17fd2 100644
--- a/drivers/i3c/master/svc-i3c-master.c
+++ b/drivers/i3c/master/svc-i3c-master.c
@@ -21,6 +21,8 @@
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
+#include "svc-i3c.h"
+
/* Master Mode Registers */
#define SVC_I3C_MCONFIG 0x000
#define SVC_I3C_MCONFIG_MASTER_EN BIT(0)
@@ -1613,7 +1615,7 @@ static void svc_i3c_master_unprepare_clks(struct svc_i3c_master *master)
clk_disable_unprepare(master->sclk);
}
-static int svc_i3c_master_probe(struct platform_device *pdev)
+int svc_i3c_master_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct svc_i3c_master *master;
@@ -1706,7 +1708,7 @@ static int svc_i3c_master_probe(struct platform_device *pdev)
return ret;
}
-static void svc_i3c_master_remove(struct platform_device *pdev)
+void svc_i3c_master_remove(struct platform_device *pdev)
{
struct svc_i3c_master *master = platform_get_drvdata(pdev);
@@ -1733,7 +1735,7 @@ static void svc_i3c_restore_regs(struct svc_i3c_master *master)
}
}
-static int __maybe_unused svc_i3c_runtime_suspend(struct device *dev)
+int svc_i3c_master_runtime_suspend(struct device *dev)
{
struct svc_i3c_master *master = dev_get_drvdata(dev);
@@ -1744,7 +1746,7 @@ static int __maybe_unused svc_i3c_runtime_suspend(struct device *dev)
return 0;
}
-static int __maybe_unused svc_i3c_runtime_resume(struct device *dev)
+int svc_i3c_master_runtime_resume(struct device *dev)
{
struct svc_i3c_master *master = dev_get_drvdata(dev);
@@ -1756,30 +1758,6 @@ static int __maybe_unused svc_i3c_runtime_resume(struct device *dev)
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-v1"},
- { /* sentinel */ },
-};
-MODULE_DEVICE_TABLE(of, svc_i3c_master_of_match_tbl);
-
-static struct platform_driver svc_i3c_master = {
- .probe = svc_i3c_master_probe,
- .remove_new = svc_i3c_master_remove,
- .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);
-
MODULE_AUTHOR("Conor Culhane <[email protected]>");
MODULE_AUTHOR("Miquel Raynal <[email protected]>");
MODULE_DESCRIPTION("Silvaco dual-role I3C master driver");
diff --git a/drivers/i3c/master/svc-i3c.h b/drivers/i3c/master/svc-i3c.h
new file mode 100644
index 0000000000000..b9e1dce4b2109
--- /dev/null
+++ b/drivers/i3c/master/svc-i3c.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef SVC_I3C_H
+#define SVC_I3C_H
+
+int svc_i3c_master_probe(struct platform_device *pdev);
+void svc_i3c_master_remove(struct platform_device *pdev);
+int svc_i3c_master_runtime_suspend(struct device *dev);
+int svc_i3c_master_runtime_resume(struct device *dev);
+
+#endif
+
--
2.34.1
I3C standard 5.1.9.3.15 Get Device Status (GETSTATUS):
Get request for one I3C Target Device to return its current status.
Add API to fetch it with format1.
Signed-off-by: Frank Li <[email protected]>
---
drivers/i3c/device.c | 24 ++++++++++++++++++++++++
drivers/i3c/internals.h | 1 +
drivers/i3c/master.c | 26 ++++++++++++++++++++++++++
include/linux/i3c/device.h | 1 +
4 files changed, 52 insertions(+)
diff --git a/drivers/i3c/device.c b/drivers/i3c/device.c
index 1a6a8703dbc3a..aa26cf50ab9c6 100644
--- a/drivers/i3c/device.c
+++ b/drivers/i3c/device.c
@@ -196,6 +196,30 @@ void i3c_device_free_ibi(struct i3c_device *dev)
}
EXPORT_SYMBOL_GPL(i3c_device_free_ibi);
+/**
+ * i3c_device_getstatus_format1() - Get device status with format 1.
+ * @dev: device for which you want to get status.
+ * @status: I3C status format 1
+ *
+ * Return: 0 in case of success, a negative error core otherwise.
+ */
+int i3c_device_getstatus_format1(struct i3c_device *dev, u16 *status)
+{
+ int ret = -EINVAL;
+
+ if (!status)
+ return -EINVAL;
+
+ i3c_bus_normaluse_lock(dev->bus);
+ if (dev->desc)
+ ret = i3c_dev_getstatus_format1_locked(dev->desc, status);
+
+ i3c_bus_normaluse_unlock(dev->bus);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(i3c_device_getstatus_format1);
+
/**
* i3cdev_to_dev() - Returns the device embedded in @i3cdev
* @i3cdev: I3C device
diff --git a/drivers/i3c/internals.h b/drivers/i3c/internals.h
index 908a807badaf9..976ad26ca79c2 100644
--- a/drivers/i3c/internals.h
+++ b/drivers/i3c/internals.h
@@ -24,4 +24,5 @@ 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_getstatus_format1_locked(struct i3c_dev_desc *dev, u16 *status);
#endif /* I3C_INTERNAL_H */
diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index 3afa530c5e322..ff61a218bcc6d 100644
--- a/drivers/i3c/master.c
+++ b/drivers/i3c/master.c
@@ -2988,6 +2988,32 @@ void i3c_dev_free_ibi_locked(struct i3c_dev_desc *dev)
dev->ibi = NULL;
}
+int i3c_dev_getstatus_format1_locked(struct i3c_dev_desc *dev, u16 *status)
+{
+ struct i3c_master_controller *master = i3c_dev_get_master(dev);
+ struct i3c_ccc_getstatus *format1;
+ struct i3c_ccc_cmd_dest dest;
+ struct i3c_ccc_cmd cmd;
+ int ret;
+
+ format1 = i3c_ccc_cmd_dest_init(&dest, dev->info.dyn_addr, sizeof(*format1));
+ if (!format1)
+ 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;
+
+ *status = be16_to_cpu(format1->status);
+
+out:
+ i3c_ccc_cmd_dest_cleanup(&dest);
+
+ return ret;
+}
+
static int __init i3c_init(void)
{
int res;
diff --git a/include/linux/i3c/device.h b/include/linux/i3c/device.h
index e119f11948efe..66920045ff042 100644
--- a/include/linux/i3c/device.h
+++ b/include/linux/i3c/device.h
@@ -345,4 +345,5 @@ 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_getstatus_format1(struct i3c_device *dev, u16 *status);
#endif /* I3C_DEV_H */
--
2.34.1
In typical embedded Linux systems, UART consoles require at least two pins,
TX and RX. In scenarios where I2C/I3C devices like sensors or PMICs are
present, we can save these two pins by using this driver. Pins is crucial
resources, especially in small chip packages.
This introduces support for using the I3C bus to transfer console tty data,
effectively replacing the need for dedicated UART pins. This not only
conserves valuable pin resources but also facilitates testing of I3C's
advanced features, including early termination, in-band interrupt (IBI)
support, and the creation of more complex data patterns. Additionally,
it aids in identifying and addressing issues within the I3C controller
driver.
Signed-off-by: Frank Li <[email protected]>
---
Notes:
Notes:
Version number use i3c target patches.
Change from v6 to v7
- try to fixed build warning
https://lore.kernel.org/imx/[email protected]/T/#m4e35da31a4226186b3dc6bc8f2fba28f58be1188
but I can't reproduce this error in my machine with powerpc-linux-gcc (GCC) 13.2.0
kernel test robot have not show reproduce step.
But anyway, status may not init if code through continue branch. So
Move status = I3C_TTY_TARGET_RX_READY; before continue branch.
Change from v5 to v6
-none
Change from v4 to v5
- add missed header file
- add define for BIT(0)
- oneline for struct ttyi3c_port *sport
Change from v3 to v4
- add static at i3c_remove()
Change v2
- using system_unbound_wq working queue
- fixed accoring to Jiri Slaby's comments
Change before send with i3c target support
Change from v4 to v5
- send in i3c improvememtn patches.
Change from v2 to v4
- none
Change from v1 to v2
- update commit message.
- using goto for err handle
- using one working queue for all tty-i3c device
- fixed typo found by js
- update kconfig help
- using kfifo
Still below items not be fixed (according to Jiri Slaby's comments)
- rxwork thread: need trigger from two position.
- common thread queue: need some suggestion
Notes:
Version number use i3c target patches.
Change from v3 to v4
- add static at i3c_remove()
Change v2
- using system_unbound_wq working queue
- fixed accoring to Jiri Slaby's comments
Change before send with i3c target support
Change from v4 to v5
- send in i3c improvememtn patches.
Change from v2 to v4
- none
Change from v1 to v2
- update commit message.
- using goto for err handle
- using one working queue for all tty-i3c device
- fixed typo found by js
- update kconfig help
- using kfifo
Still below items not be fixed (according to Jiri Slaby's comments)
- rxwork thread: need trigger from two position.
- common thread queue: need some suggestion
drivers/tty/Kconfig | 13 ++
drivers/tty/Makefile | 1 +
drivers/tty/i3c_tty.c | 428 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 442 insertions(+)
create mode 100644 drivers/tty/i3c_tty.c
diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig
index 5646dc6242cd9..9ab4cd480e9f8 100644
--- a/drivers/tty/Kconfig
+++ b/drivers/tty/Kconfig
@@ -412,6 +412,19 @@ config RPMSG_TTY
To compile this driver as a module, choose M here: the module will be
called rpmsg_tty.
+config I3C_TTY
+ tristate "TTY over I3C"
+ depends on I3C
+ help
+ Select this option to use TTY over I3C master controller.
+
+ This makes it possible for user-space programs to send and receive
+ data as a standard tty protocol. I3C provide relatively higher data
+ transfer rate and less pin numbers, SDA/SCL are shared with other
+ devices.
+
+ If unsure, say N
+
endif # TTY
source "drivers/tty/serdev/Kconfig"
diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile
index 07aca5184a55d..f329f9c7d308a 100644
--- a/drivers/tty/Makefile
+++ b/drivers/tty/Makefile
@@ -27,5 +27,6 @@ obj-$(CONFIG_GOLDFISH_TTY) += goldfish.o
obj-$(CONFIG_MIPS_EJTAG_FDC_TTY) += mips_ejtag_fdc.o
obj-$(CONFIG_VCC) += vcc.o
obj-$(CONFIG_RPMSG_TTY) += rpmsg_tty.o
+obj-$(CONFIG_I3C_TTY) += i3c_tty.o
obj-y += ipwireless/
diff --git a/drivers/tty/i3c_tty.c b/drivers/tty/i3c_tty.c
new file mode 100644
index 0000000000000..4d57624ae6c8f
--- /dev/null
+++ b/drivers/tty/i3c_tty.c
@@ -0,0 +1,428 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2023 NXP.
+ *
+ * Author: Frank Li <[email protected]>
+ */
+
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/i3c/device.h>
+#include <linux/i3c/master.h>
+#include <linux/slab.h>
+#include <linux/console.h>
+#include <linux/serial_core.h>
+#include <linux/workqueue.h>
+#include <linux/tty_flip.h>
+#include <linux/tty_port.h>
+#include <linux/spinlock.h>
+
+static DEFINE_IDR(i3c_tty_minors);
+static DEFINE_MUTEX(i3c_tty_minors_lock);
+
+static struct tty_driver *i3c_tty_driver;
+
+#define I3C_TTY_MINORS 8
+#define I3C_TTY_TRANS_SIZE 16
+#define I3C_TTY_RX_STOP 0
+#define I3C_TTY_RETRY 20
+#define I3C_TTY_YIELD_US 100
+#define I3C_TTY_TARGET_RX_READY BIT(0)
+
+struct ttyi3c_port {
+ struct tty_port port;
+ int minor;
+ spinlock_t xlock; /* protect xmit */
+ u8 tx_buff[I3C_TTY_TRANS_SIZE];
+ u8 rx_buff[I3C_TTY_TRANS_SIZE];
+ struct i3c_device *i3cdev;
+ struct work_struct txwork;
+ struct work_struct rxwork;
+ struct completion txcomplete;
+ unsigned long status;
+ u32 buf_overrun;
+};
+
+static const struct i3c_device_id i3c_ids[] = {
+ I3C_DEVICE(0x011B, 0x1000, NULL),
+ { /* sentinel */ },
+};
+
+static int i3c_port_activate(struct tty_port *port, struct tty_struct *tty)
+{
+ struct ttyi3c_port *sport = container_of(port, struct ttyi3c_port, port);
+ int ret;
+
+ ret = tty_port_alloc_xmit_buf(port);
+ if (ret < 0)
+ return ret;
+
+ sport->status = 0;
+
+ ret = i3c_device_enable_ibi(sport->i3cdev);
+ if (ret) {
+ tty_port_free_xmit_buf(port);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void i3c_port_shutdown(struct tty_port *port)
+{
+ struct ttyi3c_port *sport = container_of(port, struct ttyi3c_port, port);
+
+ i3c_device_disable_ibi(sport->i3cdev);
+ tty_port_free_xmit_buf(port);
+}
+
+static void i3c_port_destruct(struct tty_port *port)
+{
+ struct ttyi3c_port *sport = container_of(port, struct ttyi3c_port, port);
+
+ mutex_lock(&i3c_tty_minors_lock);
+ idr_remove(&i3c_tty_minors, sport->minor);
+ mutex_unlock(&i3c_tty_minors_lock);
+}
+
+static const struct tty_port_operations i3c_port_ops = {
+ .shutdown = i3c_port_shutdown,
+ .activate = i3c_port_activate,
+ .destruct = i3c_port_destruct,
+};
+
+static ssize_t i3c_write(struct tty_struct *tty, const unsigned char *buf, size_t count)
+{
+ struct ttyi3c_port *sport = tty->driver_data;
+ unsigned long flags;
+ bool is_empty;
+ int ret;
+
+ spin_lock_irqsave(&sport->xlock, flags);
+ ret = kfifo_in(&sport->port.xmit_fifo, buf, count);
+ is_empty = kfifo_is_empty(&sport->port.xmit_fifo);
+ spin_unlock_irqrestore(&sport->xlock, flags);
+
+ if (!is_empty)
+ queue_work(system_unbound_wq, &sport->txwork);
+
+ return ret;
+}
+
+static int i3c_put_char(struct tty_struct *tty, unsigned char ch)
+{
+ struct ttyi3c_port *sport = tty->driver_data;
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&sport->xlock, flags);
+ ret = kfifo_put(&sport->port.xmit_fifo, ch);
+ spin_unlock_irqrestore(&sport->xlock, flags);
+
+ return ret;
+}
+
+static void i3c_flush_chars(struct tty_struct *tty)
+{
+ struct ttyi3c_port *sport = tty->driver_data;
+
+ queue_work(system_unbound_wq, &sport->txwork);
+}
+
+static unsigned int i3c_write_room(struct tty_struct *tty)
+{
+ struct ttyi3c_port *sport = tty->driver_data;
+
+ return kfifo_avail(&sport->port.xmit_fifo);
+}
+
+static void i3c_throttle(struct tty_struct *tty)
+{
+ struct ttyi3c_port *sport = tty->driver_data;
+
+ clear_bit(I3C_TTY_RX_STOP, &sport->status);
+}
+
+static void i3c_unthrottle(struct tty_struct *tty)
+{
+ struct ttyi3c_port *sport = tty->driver_data;
+
+ set_bit(I3C_TTY_RX_STOP, &sport->status);
+
+ queue_work(system_unbound_wq, &sport->rxwork);
+}
+
+static int i3c_open(struct tty_struct *tty, struct file *filp)
+{
+ struct ttyi3c_port *sport = container_of(tty->port, struct ttyi3c_port, port);
+
+ tty->driver_data = sport;
+
+ return tty_port_open(&sport->port, tty, filp);
+}
+
+static void i3c_close(struct tty_struct *tty, struct file *filp)
+{
+ tty_port_close(tty->port, tty, filp);
+}
+
+static const struct tty_operations i3c_tty_ops = {
+ .open = i3c_open,
+ .close = i3c_close,
+ .write = i3c_write,
+ .put_char = i3c_put_char,
+ .flush_chars = i3c_flush_chars,
+ .write_room = i3c_write_room,
+ .throttle = i3c_throttle,
+ .unthrottle = i3c_unthrottle,
+};
+
+static void i3c_controller_irq_handler(struct i3c_device *dev,
+ const struct i3c_ibi_payload *payload)
+{
+ struct ttyi3c_port *sport = dev_get_drvdata(&dev->dev);
+
+ /* i3c_unthrottle also queue the work to fetch pending data in target side */
+ queue_work(system_unbound_wq, &sport->rxwork);
+}
+
+static void tty_i3c_rxwork(struct work_struct *work)
+{
+ struct ttyi3c_port *sport = container_of(work, struct ttyi3c_port, rxwork);
+ struct i3c_priv_xfer xfers;
+ u32 retry = I3C_TTY_RETRY;
+ u16 status;
+ int ret;
+
+ memset(&xfers, 0, sizeof(xfers));
+ xfers.data.in = sport->rx_buff;
+ xfers.len = I3C_TTY_TRANS_SIZE;
+ xfers.rnw = 1;
+
+ do {
+ if (test_bit(I3C_TTY_RX_STOP, &sport->status))
+ break;
+
+ i3c_device_do_priv_xfers(sport->i3cdev, &xfers, 1);
+
+ status = I3C_TTY_TARGET_RX_READY;
+
+ if (xfers.actual_len) {
+ ret = tty_insert_flip_string(&sport->port, sport->rx_buff,
+ xfers.actual_len);
+ if (ret < xfers.actual_len)
+ sport->buf_overrun++;
+
+ retry = I3C_TTY_RETRY;
+ continue;
+ }
+
+ i3c_device_getstatus_format1(sport->i3cdev, &status);
+ /*
+ * Target side needs some time to fill data into fifo. Target side may not
+ * have hardware update status in real time. Software update status always
+ * needs some delays.
+ *
+ * Generally, target side have circular buffer in memory, it will be moved
+ * into FIFO by CPU or DMA. 'status' just show if circular buffer empty. But
+ * there are gap, especially CPU have not response irq to fill FIFO in time.
+ * So xfers.actual will be zero, wait for little time to avoid flood
+ * transfer in i3c bus.
+ */
+ usleep_range(I3C_TTY_YIELD_US, 10 * I3C_TTY_YIELD_US);
+ retry--;
+
+ } while (retry && (status & I3C_TTY_TARGET_RX_READY));
+
+ tty_flip_buffer_push(&sport->port);
+}
+
+static void tty_i3c_txwork(struct work_struct *work)
+{
+ struct ttyi3c_port *sport = container_of(work, struct ttyi3c_port, txwork);
+ struct i3c_priv_xfer xfers;
+ u32 retry = I3C_TTY_RETRY;
+ unsigned long flags;
+ int ret;
+
+ xfers.rnw = 0;
+ xfers.data.out = sport->tx_buff;
+
+ while (!kfifo_is_empty(&sport->port.xmit_fifo)) {
+ spin_lock_irqsave(&sport->xlock, flags);
+ xfers.len = kfifo_out_peek(&sport->port.xmit_fifo, sport->tx_buff,
+ I3C_TTY_TRANS_SIZE);
+ spin_unlock_irqrestore(&sport->xlock, flags);
+ ret = i3c_device_do_priv_xfers(sport->i3cdev, &xfers, 1);
+ if (ret) {
+ /*
+ * Target side may not move data out of FIFO. delay can't resolve problem,
+ * just reduce some possiblity. Target can't end I3C SDR mode write
+ * transfer, discard data is reasonable when FIFO overrun.
+ */
+ usleep_range(I3C_TTY_YIELD_US, 10 * I3C_TTY_YIELD_US);
+ retry--;
+ } else {
+ retry = I3C_TTY_RETRY;
+ }
+
+ if (ret == 0 || retry == 0) {
+ /* when retry == 0, means need discard the data */
+ spin_lock_irqsave(&sport->xlock, flags);
+ ret = kfifo_out(&sport->port.xmit_fifo, sport->tx_buff, xfers.len);
+ spin_unlock_irqrestore(&sport->xlock, flags);
+ }
+ }
+
+ spin_lock_irqsave(&sport->xlock, flags);
+ if (kfifo_len(&sport->port.xmit_fifo) < WAKEUP_CHARS)
+ tty_port_tty_wakeup(&sport->port);
+ spin_unlock_irqrestore(&sport->xlock, flags);
+}
+
+static int i3c_probe(struct i3c_device *i3cdev)
+{
+ struct ttyi3c_port *sport;
+ struct device *tty_dev;
+ struct i3c_ibi_setup req;
+ int minor;
+ int ret;
+
+ sport = devm_kzalloc(&i3cdev->dev, sizeof(*sport), GFP_KERNEL);
+ if (!sport)
+ return -ENOMEM;
+
+ sport->i3cdev = i3cdev;
+
+ dev_set_drvdata(&i3cdev->dev, sport);
+
+ req.max_payload_len = 8;
+ req.num_slots = 4;
+ req.handler = &i3c_controller_irq_handler;
+
+ ret = i3c_device_request_ibi(i3cdev, &req);
+ if (ret)
+ return -EINVAL;
+
+ mutex_lock(&i3c_tty_minors_lock);
+ minor = idr_alloc(&i3c_tty_minors, sport, 0, I3C_TTY_MINORS, GFP_KERNEL);
+ mutex_unlock(&i3c_tty_minors_lock);
+
+ if (minor < 0) {
+ ret = -EINVAL;
+ goto err_idr_alloc;
+ }
+
+ spin_lock_init(&sport->xlock);
+ INIT_WORK(&sport->txwork, tty_i3c_txwork);
+ INIT_WORK(&sport->rxwork, tty_i3c_rxwork);
+ init_completion(&sport->txcomplete);
+
+ tty_port_init(&sport->port);
+ sport->port.ops = &i3c_port_ops;
+
+ tty_dev = tty_port_register_device(&sport->port, i3c_tty_driver, minor,
+ &i3cdev->dev);
+ if (IS_ERR(tty_dev)) {
+ ret = PTR_ERR(tty_dev);
+ goto err_tty_port_register;
+ }
+
+ sport->minor = minor;
+
+ return 0;
+
+err_tty_port_register:
+ tty_port_put(&sport->port);
+
+ mutex_lock(&i3c_tty_minors_lock);
+ idr_remove(&i3c_tty_minors, minor);
+ mutex_unlock(&i3c_tty_minors_lock);
+
+err_idr_alloc:
+ i3c_device_free_ibi(i3cdev);
+
+ return ret;
+}
+
+static void i3c_remove(struct i3c_device *dev)
+{
+ struct ttyi3c_port *sport = dev_get_drvdata(&dev->dev);
+
+ tty_port_unregister_device(&sport->port, i3c_tty_driver, sport->minor);
+ cancel_work_sync(&sport->txwork);
+
+ tty_port_put(&sport->port);
+
+ mutex_lock(&i3c_tty_minors_lock);
+ idr_remove(&i3c_tty_minors, sport->minor);
+ mutex_unlock(&i3c_tty_minors_lock);
+
+ i3c_device_free_ibi(sport->i3cdev);
+}
+
+static struct i3c_driver i3c_driver = {
+ .driver = {
+ .name = "ttyi3c",
+ },
+ .probe = i3c_probe,
+ .remove = i3c_remove,
+ .id_table = i3c_ids,
+};
+
+static int __init i3c_tty_init(void)
+{
+ int ret;
+
+ i3c_tty_driver = tty_alloc_driver(I3C_TTY_MINORS,
+ TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV);
+
+ if (IS_ERR(i3c_tty_driver))
+ return PTR_ERR(i3c_tty_driver);
+
+ i3c_tty_driver->driver_name = "ttyI3C";
+ i3c_tty_driver->name = "ttyI3C";
+ i3c_tty_driver->minor_start = 0,
+ i3c_tty_driver->type = TTY_DRIVER_TYPE_SERIAL,
+ i3c_tty_driver->subtype = SERIAL_TYPE_NORMAL,
+ i3c_tty_driver->init_termios = tty_std_termios;
+ i3c_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL |
+ CLOCAL;
+ i3c_tty_driver->init_termios.c_lflag = 0;
+
+ tty_set_operations(i3c_tty_driver, &i3c_tty_ops);
+
+ ret = tty_register_driver(i3c_tty_driver);
+ if (ret)
+ goto err_tty_register_driver;
+
+ ret = i3c_driver_register(&i3c_driver);
+ if (ret)
+ goto err_i3c_driver_register;
+
+ return 0;
+
+err_i3c_driver_register:
+ tty_unregister_driver(i3c_tty_driver);
+
+err_tty_register_driver:
+ tty_driver_kref_put(i3c_tty_driver);
+
+ return ret;
+}
+
+static void __exit i3c_tty_exit(void)
+{
+ i3c_driver_unregister(&i3c_driver);
+ tty_unregister_driver(i3c_tty_driver);
+ tty_driver_kref_put(i3c_tty_driver);
+ idr_destroy(&i3c_tty_minors);
+}
+
+module_init(i3c_tty_init);
+module_exit(i3c_tty_exit);
+
+MODULE_LICENSE("GPL");
--
2.34.1
Add Silvaco I3C target controller support
Signed-off-by: Frank Li <[email protected]>
---
Notes:
Change from v2 to v3
- fix build warning
drivers/i3c/master/Makefile | 2 +-
drivers/i3c/master/svc-i3c-main.c | 35 +-
drivers/i3c/master/svc-i3c-target.c | 776 ++++++++++++++++++++++++++++
drivers/i3c/master/svc-i3c.h | 3 +
4 files changed, 811 insertions(+), 5 deletions(-)
create mode 100644 drivers/i3c/master/svc-i3c-target.c
diff --git a/drivers/i3c/master/Makefile b/drivers/i3c/master/Makefile
index 484cb81f45821..b9ed6c9e7be13 100644
--- a/drivers/i3c/master/Makefile
+++ b/drivers/i3c/master/Makefile
@@ -2,6 +2,6 @@
obj-$(CONFIG_CDNS_I3C_MASTER) += i3c-master-cdns.o
obj-$(CONFIG_DW_I3C_MASTER) += dw-i3c-master.o
obj-$(CONFIG_AST2600_I3C_MASTER) += ast2600-i3c-master.o
-svc-i3c-objs += svc-i3c-main.o svc-i3c-master.o
+svc-i3c-objs += svc-i3c-main.o svc-i3c-master.o svc-i3c-target.o
obj-$(CONFIG_SVC_I3C_MASTER) += svc-i3c.o
obj-$(CONFIG_MIPI_I3C_HCI) += mipi-i3c-hci/
diff --git a/drivers/i3c/master/svc-i3c-main.c b/drivers/i3c/master/svc-i3c-main.c
index 6be6a576cdf7a..e34b128ab274a 100644
--- a/drivers/i3c/master/svc-i3c-main.c
+++ b/drivers/i3c/master/svc-i3c-main.c
@@ -7,24 +7,51 @@
#include "svc-i3c.h"
+static bool svc_i3c_is_master(struct device *dev)
+{
+ const char *mode = NULL;
+
+ device_property_read_string(dev, "mode", &mode);
+
+ if (!mode)
+ return true;
+
+ if (strncmp(mode, "target", 6) == 0)
+ return false;
+
+ return true;
+}
+
static int svc_i3c_probe(struct platform_device *pdev)
{
- return svc_i3c_master_probe(pdev);
+ if (svc_i3c_is_master(&pdev->dev))
+ return svc_i3c_master_probe(pdev);
+
+ return svc_i3c_target_probe(pdev);
}
static void svc_i3c_remove(struct platform_device *pdev)
{
- svc_i3c_master_remove(pdev);
+ if (svc_i3c_is_master(&pdev->dev))
+ return svc_i3c_master_remove(pdev);
+
+ svc_i3c_target_remove(pdev);
}
static int __maybe_unused svc_i3c_runtime_suspend(struct device *dev)
{
- return svc_i3c_master_runtime_suspend(dev);
+ if (svc_i3c_is_master(dev))
+ return svc_i3c_master_runtime_suspend(dev);
+
+ return -EINVAL;
}
static int __maybe_unused svc_i3c_runtime_resume(struct device *dev)
{
- return svc_i3c_master_runtime_resume(dev);
+ if (svc_i3c_is_master(dev))
+ return svc_i3c_master_runtime_resume(dev);
+
+ return -EINVAL;
}
static const struct dev_pm_ops svc_i3c_pm_ops = {
diff --git a/drivers/i3c/master/svc-i3c-target.c b/drivers/i3c/master/svc-i3c-target.c
new file mode 100644
index 0000000000000..094efb60cb404
--- /dev/null
+++ b/drivers/i3c/master/svc-i3c-target.c
@@ -0,0 +1,776 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2023 NXP.
+ *
+ * Author: Frank Li <[email protected]>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/errno.h>
+#include <linux/i3c/target.h>
+#include <linux/i3c/target.h>
+#include <linux/interrupt.h>
+#include <linux/iopoll.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/clk.h>
+#include <linux/console.h>
+#include <linux/i3c/device.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+
+#include "svc-i3c.h"
+
+enum i3c_clks {
+ PCLK,
+ FCLK,
+ SCLK,
+ MAXCLK,
+};
+
+struct svc_i3c_target {
+ struct device *dev;
+ void __iomem *regs;
+ int irq;
+ struct clk_bulk_data clks[MAXCLK];
+
+ struct list_head txq;
+ spinlock_t txq_lock; /* protect tx queue */
+ struct list_head rxq;
+ spinlock_t rxq_lock; /* protect rx queue */
+ struct list_head cq;
+ spinlock_t cq_lock; /* protect complete queue */
+
+ struct work_struct work;
+ struct workqueue_struct *workqueue;
+
+ struct completion dacomplete;
+ struct i3c_target_ctrl_features features;
+
+ spinlock_t ctrl_lock; /* protext access SCTRL register */
+};
+
+#define I3C_SCONFIG 0x4
+#define I3C_SCONFIG_SLVENA_MASK BIT(0)
+#define I3C_SCONFIG_OFFLINE_MASK BIT(9)
+#define I3C_SCONFIG_SADDR_MASK GENMASK(31, 25)
+
+#define I3C_SSTATUS 0x8
+#define I3C_SSTATUS_STNOTSTOP_MASK BIT(0)
+#define I3C_SSTATUS_STOP_MASK BIT(10)
+#define I3C_SSTATUS_RX_PEND_MASK BIT(11)
+#define I3C_SSTATUS_TXNOTFULL_MASK BIT(12)
+#define I3C_SSTATUS_DACHG_MASK BIT(13)
+#define I3C_SSTATUS_EVDET_MASK GENMASK(21, 20)
+#define I3C_SSTATUS_EVDET_ACKED 0x3
+#define I3C_SSTATUS_IBIDIS_MASK BIT(24)
+#define I3C_SSTATUS_HJDIS_MASK BIT(27)
+
+#define I3C_SCTRL 0xc
+#define I3C_SCTRL_EVENT_MASK GENMASK(1, 0)
+#define I3C_SCTRL_EVENT_IBI 0x1
+#define I3C_SCTRL_EVENT_HOTJOIN 0x3
+#define I3C_SCTRL_EXTDATA_MASK BIT(3)
+#define I3C_SCTRL_IBIDATA_MASK GENMASK(15, 8)
+
+#define I3C_SINTSET 0x10
+#define I3C_SINTCLR 0x14
+#define I3C_SINT_START BIT(8)
+#define I3C_SINT_MATCHED BIT(9)
+#define I3C_SINT_STOP BIT(10)
+#define I3C_SINT_RXPEND BIT(11)
+#define I3C_SINT_TXSEND BIT(12)
+#define I3C_SINT_DACHG BIT(13)
+#define I3C_SINT_CCC BIT(14)
+#define I3C_SINT_ERRWARN BIT(15)
+#define I3C_SINT_DDRMAATCHED BIT(16)
+#define I3C_SINT_CHANDLED BIT(17)
+#define I3C_SINT_EVENT BIT(18)
+#define I3C_SINT_SLVRST BIT(19)
+
+#define I3C_SDATACTRL 0x2c
+#define I3C_SDATACTRL_RXEMPTY_MASK BIT(31)
+#define I3C_SDATACTRL_TXFULL_MASK BIT(30)
+#define I3C_SDATACTRL_RXCOUNT_MASK GENMASK(28, 24)
+#define I3C_SDATACTRL_TXCOUNT_MASK GENMASK(20, 16)
+#define I3C_SDATACTRL_FLUSHFB_MASK BIT(1)
+#define I3C_SDATACTRL_FLUSHTB_MASK BIT(0)
+
+#define I3C_SWDATAB 0x30
+#define I3C_SWDATAB_END_ALSO_MASK BIT(16)
+#define I3C_SWDATAB_END_MASK BIT(8)
+
+#define I3C_SWDATAE 0x34
+#define I3C_SRDATAB 0x40
+
+#define I3C_SCAPABILITIES 0x60
+#define I3C_SCAPABILITIES_FIFOTX_MASK GENMASK(27, 26)
+#define I3C_SCAPABILITIES_FIFORX_MASK GENMASK(29, 28)
+
+#define I3C_SMAXLIMITS 0x68
+#define I3C_SMAXLIMITS_MAXRD_MASK GENMASK(11, 0)
+#define I3C_SMAXLIMITS_MAXWR_MASK GENMASK(27, 16)
+
+#define I3C_SIDPARTNO 0x6c
+
+#define I3C_SIDEXT 0x70
+#define I3C_SIDEXT_BCR_MASK GENMASK(23, 16)
+#define I3C_SIDEXT_DCR_MASK GENMASK(15, 8)
+#define I3C_SVENDORID 0x74
+
+#define I3C_SMAPCTRL0 0x11c
+#define I3C_SMAPCTRL0_ENA_MASK BIT(0)
+#define I3C_SMAPCTRL0_DA_MASK GENMASK(7, 1)
+
+#define I3C_IBIEXT1 0x140
+#define I3C_IBIEXT1_CNT_MASK GEN_MASK(2, 0)
+#define I3C_IBIEXT1_MAX_MASK GEN_MASK(4, 6)
+#define I3C_IBIEXT1_EXT1_SHIFT 8
+#define I3C_IBIEXT1_EXT2_SHIFT 16
+#define I3C_IBIEXT1_EXT3_SHIFT 24
+
+#define I3C_IBIEXT2 0x144
+#define I3C_IBIEXT2_EXT4_SHIFT 0
+#define I3C_IBIEXT2_EXT5_SHIFT 8
+#define I3C_IBIEXT2_EXT6_SHIFT 16
+#define I3C_IBIEXT2_EXT7_SHIFT 24
+
+static int svc_i3c_target_enable(struct i3c_target_ctrl *ctrl)
+{
+ struct svc_i3c_target *svc;
+ u32 val;
+
+ svc = dev_get_drvdata(&ctrl->dev);
+
+ val = readl_relaxed(svc->regs + I3C_SCONFIG);
+ val |= I3C_SCONFIG_SLVENA_MASK;
+ writel_relaxed(val, svc->regs + I3C_SCONFIG);
+
+ return 0;
+}
+
+static int svc_i3c_target_disable(struct i3c_target_ctrl *ctrl)
+{
+ struct svc_i3c_target *svc;
+ u32 val;
+
+ svc = dev_get_drvdata(&ctrl->dev);
+
+ val = readl_relaxed(svc->regs + I3C_SCONFIG);
+ val &= ~I3C_SCONFIG_SLVENA_MASK;
+ writel_relaxed(val, svc->regs + I3C_SCONFIG);
+
+ return 0;
+}
+
+static int svc_i3c_target_set_config(struct i3c_target_ctrl *ctrl, struct i3c_target_func *func)
+{
+ struct svc_i3c_target *svc;
+ u32 val;
+ u32 wm, rm;
+
+ svc = dev_get_drvdata(&ctrl->dev);
+
+ if (func->static_addr > 0x7F)
+ return -EINVAL;
+
+ val = readl_relaxed(svc->regs + I3C_SCONFIG);
+ val &= ~I3C_SCONFIG_SLVENA_MASK;
+ val |= FIELD_PREP(I3C_SCONFIG_SADDR_MASK, func->static_addr);
+ writel_relaxed(val, svc->regs + I3C_SCONFIG);
+
+ if (func->part_id)
+ writel_relaxed((func->part_id << 16) |
+ ((func->instance_id << 12) & GENMASK(15, 12)) |
+ (func->ext_id & GENMASK(11, 0)), svc->regs + I3C_SIDPARTNO);
+
+ writel_relaxed(FIELD_PREP(I3C_SIDEXT_BCR_MASK, func->bcr) |
+ FIELD_PREP(I3C_SIDEXT_DCR_MASK, func->dcr),
+ svc->regs + I3C_SIDEXT);
+
+ wm = func->max_write_len == 0 ?
+ FIELD_GET(I3C_SMAXLIMITS_MAXWR_MASK, I3C_SMAXLIMITS_MAXWR_MASK) : func->max_write_len;
+
+ wm = max_t(u32, val, 8);
+
+ rm = func->max_read_len == 0 ?
+ FIELD_GET(I3C_SMAXLIMITS_MAXRD_MASK, I3C_SMAXLIMITS_MAXRD_MASK) : func->max_read_len;
+ rm = max_t(u32, val, 16);
+
+ val = FIELD_PREP(I3C_SMAXLIMITS_MAXRD_MASK, rm) | FIELD_PREP(I3C_SMAXLIMITS_MAXWR_MASK, wm);
+ writel_relaxed(val, svc->regs + I3C_SMAXLIMITS);
+
+ writel_relaxed(func->vendor_id, svc->regs + I3C_SVENDORID);
+ return 0;
+}
+
+static const struct i3c_target_ctrl_features *svc_i3c_get_features(struct i3c_target_ctrl *ctrl)
+{
+ struct svc_i3c_target *svc;
+
+ svc = dev_get_drvdata(&ctrl->dev);
+
+ if (!svc)
+ return NULL;
+
+ return &svc->features;
+}
+
+static void svc_i3c_queue_complete(struct svc_i3c_target *svc, struct i3c_request *complete)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&svc->cq_lock, flags);
+ list_add_tail(&complete->list, &svc->cq);
+ spin_unlock_irqrestore(&svc->cq_lock, flags);
+ queue_work(svc->workqueue, &svc->work);
+}
+
+static void svc_i3c_fill_txfifo(struct svc_i3c_target *svc)
+{
+ struct i3c_request *req, *complete = NULL;
+ unsigned long flags;
+ int val;
+
+ spin_lock_irqsave(&svc->txq_lock, flags);
+ while ((!!(req = list_first_entry_or_null(&svc->txq, struct i3c_request, list))) &&
+ !((readl_relaxed(svc->regs + I3C_SDATACTRL) & I3C_SDATACTRL_TXFULL_MASK))) {
+ while (!(readl_relaxed(svc->regs + I3C_SDATACTRL)
+ & I3C_SDATACTRL_TXFULL_MASK)) {
+ val = *(u8 *)(req->buf + req->actual);
+
+ if (req->actual + 1 == req->length)
+ writel_relaxed(val, svc->regs + I3C_SWDATAE);
+ else
+ writel_relaxed(val, svc->regs + I3C_SWDATAB);
+
+ req->actual++;
+
+ if (req->actual == req->length) {
+ list_del(&req->list);
+ complete = req;
+ spin_unlock_irqrestore(&svc->txq_lock, flags);
+
+ svc_i3c_queue_complete(svc, complete);
+ return;
+
+ spin_lock_irqsave(&svc->txq_lock, flags);
+ break;
+ }
+ }
+ }
+ spin_unlock_irqrestore(&svc->txq_lock, flags);
+}
+
+static int svc_i3c_target_queue(struct i3c_request *req, gfp_t gfp)
+{
+ struct svc_i3c_target *svc;
+ struct list_head *q;
+ unsigned long flags;
+ spinlock_t *lk;
+
+ svc = dev_get_drvdata(&req->ctrl->dev);
+ if (!svc)
+ return -EINVAL;
+
+ if (req->tx) {
+ q = &svc->txq;
+ lk = &svc->txq_lock;
+ } else {
+ q = &svc->rxq;
+ lk = &svc->rxq_lock;
+ }
+
+ spin_lock_irqsave(lk, flags);
+ list_add_tail(&req->list, q);
+ spin_unlock_irqrestore(lk, flags);
+
+ if (req->tx)
+ svc_i3c_fill_txfifo(svc);
+
+ if (req->tx)
+ writel_relaxed(I3C_SINT_TXSEND, svc->regs + I3C_SINTSET);
+ else
+ writel_relaxed(I3C_SINT_RXPEND, svc->regs + I3C_SINTSET);
+
+ return 0;
+}
+
+static int svc_i3c_dequeue(struct i3c_request *req)
+{
+ struct svc_i3c_target *svc;
+ unsigned long flags;
+ spinlock_t *lk;
+
+ svc = dev_get_drvdata(&req->ctrl->dev);
+ if (!svc)
+ return -EINVAL;
+
+ if (req->tx)
+ lk = &svc->txq_lock;
+ else
+ lk = &svc->rxq_lock;
+
+ spin_lock_irqsave(lk, flags);
+ list_del(&req->list);
+ spin_unlock_irqrestore(lk, flags);
+
+ return 0;
+}
+
+static void svc_i3c_target_fifo_flush(struct i3c_target_ctrl *ctrl, bool tx)
+{
+ struct svc_i3c_target *svc;
+ u32 val;
+
+ svc = dev_get_drvdata(&ctrl->dev);
+
+ val = readl_relaxed(svc->regs + I3C_SDATACTRL);
+
+ val |= tx ? I3C_SDATACTRL_FLUSHTB_MASK : I3C_SDATACTRL_FLUSHFB_MASK;
+
+ writel_relaxed(val, svc->regs + I3C_SDATACTRL);
+}
+
+static int
+svc_i3c_target_raise_ibi(struct i3c_target_ctrl *ctrl, void *p, u8 size)
+{
+ struct svc_i3c_target *svc;
+ unsigned long flags;
+ u8 *ibidata = p;
+ u32 ext1 = 0, ext2 = 0;
+ u32 val;
+ int ret;
+
+ svc = dev_get_drvdata(&ctrl->dev);
+
+ if (size && !p)
+ return -EINVAL;
+
+ if (size > 8)
+ return -EINVAL;
+
+ val = readl_relaxed(svc->regs + I3C_SSTATUS);
+ if (val & I3C_SSTATUS_IBIDIS_MASK)
+ return -EINVAL;
+
+ ret = readl_relaxed_poll_timeout(svc->regs + I3C_SCTRL, val,
+ !(val & I3C_SCTRL_EVENT_MASK), 0, 10000);
+ if (ret) {
+ dev_err(&ctrl->dev, "Timeout when polling for NO event pending");
+ val &= ~I3C_SCTRL_EVENT_MASK;
+ writel_relaxed(val, svc->regs + I3C_SCTRL);
+ return -ENAVAIL;
+ }
+
+ spin_lock_irqsave(&svc->ctrl_lock, flags);
+
+ val = readl_relaxed(svc->regs + I3C_SCTRL);
+
+ val &= ~I3C_SCTRL_EVENT_MASK | I3C_SCTRL_IBIDATA_MASK;
+ val |= FIELD_PREP(I3C_SCTRL_EVENT_MASK, I3C_SCTRL_EVENT_IBI);
+
+ if (size) {
+ val |= FIELD_PREP(I3C_SCTRL_IBIDATA_MASK, *ibidata);
+ ibidata++;
+
+ if (size > 1)
+ val |= I3C_SCTRL_EXTDATA_MASK;
+
+ size--;
+ if (size > 0) {
+ ext1 |= (size + 2);
+ ext1 |= (*ibidata++) << I3C_IBIEXT1_EXT1_SHIFT;
+ size--;
+ }
+
+ if (size > 0) {
+ ext1 |= (*ibidata++) << I3C_IBIEXT1_EXT2_SHIFT;
+ size--;
+ }
+
+ if (size > 0) {
+ ext1 |= (*ibidata++) << I3C_IBIEXT1_EXT3_SHIFT;
+ size--;
+ }
+
+ writel_relaxed(ext1, svc->regs + I3C_IBIEXT1);
+
+ if (size > 0) {
+ ext2 |= (*ibidata++) << I3C_IBIEXT2_EXT4_SHIFT;
+ size--;
+ }
+
+ if (size > 0) {
+ ext2 |= (*ibidata++) << I3C_IBIEXT2_EXT5_SHIFT;
+ size--;
+ }
+
+ if (size > 0) {
+ ext2 |= (*ibidata++) << I3C_IBIEXT2_EXT6_SHIFT;
+ size--;
+ }
+
+ if (size > 0) {
+ ext2 |= (*ibidata++) << I3C_IBIEXT2_EXT7_SHIFT;
+ size--;
+ }
+
+ writeb_relaxed(ext2, svc->regs + I3C_IBIEXT2);
+ }
+
+ /* Issue IBI*/
+ writel_relaxed(val, svc->regs + I3C_SCTRL);
+ spin_unlock_irqrestore(&svc->ctrl_lock, flags);
+
+ ret = readl_relaxed_poll_timeout(svc->regs + I3C_SCTRL, val,
+ !(val & I3C_SCTRL_EVENT_MASK), 0, 1000000);
+ if (ret) {
+ dev_err(&ctrl->dev, "Timeout when polling for IBI finish\n");
+
+ //clear event to above hang bus
+ spin_lock_irqsave(&svc->ctrl_lock, flags);
+ val = readl_relaxed(svc->regs + I3C_SCTRL);
+ val &= ~I3C_SCTRL_EVENT_MASK;
+ writel_relaxed(val, svc->regs + I3C_SCTRL);
+ spin_unlock_irqrestore(&svc->ctrl_lock, flags);
+
+ return -ENAVAIL;
+ }
+
+ return 0;
+}
+
+static void svc_i3c_target_complete(struct work_struct *work)
+{
+ struct svc_i3c_target *svc = container_of(work, struct svc_i3c_target, work);
+ struct i3c_request *req;
+ unsigned long flags;
+
+ spin_lock_irqsave(&svc->cq_lock, flags);
+ while (!list_empty(&svc->cq)) {
+ req = list_first_entry(&svc->cq, struct i3c_request, list);
+ list_del(&req->list);
+ spin_unlock_irqrestore(&svc->cq_lock, flags);
+ req->complete(req);
+
+ spin_lock_irqsave(&svc->cq_lock, flags);
+ }
+ spin_unlock_irqrestore(&svc->cq_lock, flags);
+}
+
+static irqreturn_t svc_i3c_target_irq_handler(int irq, void *dev_id)
+{
+ struct i3c_request *req, *complete = NULL;
+ struct svc_i3c_target *svc = dev_id;
+ unsigned long flags;
+ u32 statusFlags;
+
+ statusFlags = readl(svc->regs + I3C_SSTATUS);
+ writel(statusFlags, svc->regs + I3C_SSTATUS);
+
+ if (statusFlags & I3C_SSTATUS_DACHG_MASK)
+ complete_all(&svc->dacomplete);
+
+ if (statusFlags & I3C_SSTATUS_RX_PEND_MASK) {
+ spin_lock_irqsave(&svc->rxq_lock, flags);
+ req = list_first_entry_or_null(&svc->rxq, struct i3c_request, list);
+
+ if (!req) {
+ writel_relaxed(I3C_SINT_RXPEND, svc->regs + I3C_SINTCLR);
+ } else {
+ while (!(readl_relaxed(svc->regs + I3C_SDATACTRL) &
+ I3C_SDATACTRL_RXEMPTY_MASK)) {
+ *(u8 *)(req->buf + req->actual) =
+ readl_relaxed(svc->regs + I3C_SRDATAB);
+ req->actual++;
+
+ if (req->actual == req->length) {
+ complete = req;
+ list_del(&req->list);
+ break;
+ }
+ }
+
+ if (req->actual != req->length && (statusFlags & I3C_SSTATUS_STOP_MASK)) {
+ complete = req;
+ list_del(&req->list);
+ }
+ }
+ spin_unlock_irqrestore(&svc->rxq_lock, flags);
+
+ if (complete) {
+ spin_lock_irqsave(&svc->cq_lock, flags);
+ list_add_tail(&complete->list, &svc->cq);
+ spin_unlock_irqrestore(&svc->cq_lock, flags);
+ queue_work(svc->workqueue, &svc->work);
+ complete = NULL;
+ }
+ }
+
+ if (statusFlags & I3C_SSTATUS_TXNOTFULL_MASK) {
+ svc_i3c_fill_txfifo(svc);
+ spin_lock_irqsave(&svc->txq_lock, flags);
+ if (list_empty(&svc->txq))
+ writel_relaxed(I3C_SINT_TXSEND, svc->regs + I3C_SINTCLR);
+ spin_unlock_irqrestore(&svc->txq_lock, flags);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void svc_i3c_cancel_all_reqs(struct i3c_target_ctrl *ctrl, bool tx)
+{
+ struct svc_i3c_target *svc;
+ struct i3c_request *req;
+ struct list_head *q;
+ unsigned long flags;
+ spinlock_t *lk;
+
+ svc = dev_get_drvdata(&ctrl->dev);
+ if (!svc)
+ return;
+
+ if (tx) {
+ q = &svc->txq;
+ lk = &svc->txq_lock;
+ } else {
+ q = &svc->rxq;
+ lk = &svc->rxq_lock;
+ }
+
+ spin_lock_irqsave(lk, flags);
+ while (!list_empty(q)) {
+ req = list_first_entry(q, struct i3c_request, list);
+ list_del(&req->list);
+ spin_unlock_irqrestore(lk, flags);
+
+ req->status = I3C_REQUEST_CANCEL;
+ req->complete(req);
+ spin_lock_irqsave(lk, flags);
+ }
+ spin_unlock_irqrestore(lk, flags);
+}
+
+static int svc_i3c_hotjoin(struct i3c_target_ctrl *ctrl)
+{
+ struct svc_i3c_target *svc;
+ int ret;
+ u32 val;
+ u32 cfg;
+
+ svc = dev_get_drvdata(&ctrl->dev);
+ if (!svc)
+ return -EINVAL;
+
+ reinit_completion(&svc->dacomplete);
+
+ val = readl_relaxed(svc->regs + I3C_SSTATUS);
+ if (val & I3C_SSTATUS_HJDIS_MASK) {
+ dev_err(&ctrl->dev, "Hotjoin disabled by i3c master\n");
+ return -EINVAL;
+ }
+
+ ret = readl_relaxed_poll_timeout(svc->regs + I3C_SCTRL, val,
+ !(val & I3C_SCTRL_EVENT_MASK), 0, 10000);
+ if (ret) {
+ dev_err(&ctrl->dev, "Timeout when polling for none event pending");
+ return -ENAVAIL;
+ }
+
+ cfg = readl_relaxed(svc->regs + I3C_SCONFIG);
+ cfg |= I3C_SCONFIG_OFFLINE_MASK;
+ writel_relaxed(cfg, svc->regs + I3C_SCONFIG);
+
+ val &= ~(I3C_SCTRL_EVENT_MASK | I3C_SCTRL_IBIDATA_MASK);
+ val |= FIELD_PREP(I3C_SCTRL_EVENT_MASK, I3C_SCTRL_EVENT_HOTJOIN);
+ /* Issue hotjoin*/
+ writel_relaxed(val, svc->regs + I3C_SCTRL);
+
+ ret = readl_relaxed_poll_timeout(svc->regs + I3C_SCTRL, val,
+ !(val & I3C_SCTRL_EVENT_MASK), 0, 100000);
+ if (ret) {
+ val &= ~FIELD_PREP(I3C_SCTRL_EVENT_MASK, I3C_SCTRL_EVENT_MASK);
+ writel_relaxed(val, svc->regs + I3C_SCTRL);
+ dev_err(&ctrl->dev, "Timeout when polling for HOTJOIN finish\n");
+ return -EINVAL;
+ }
+
+ val = readl_relaxed(svc->regs + I3C_SSTATUS);
+ val = FIELD_GET(I3C_SSTATUS_EVDET_MASK, val);
+ if (val != I3C_SSTATUS_EVDET_ACKED) {
+ dev_err(&ctrl->dev, "Master NACKED hotjoin request\n");
+ return -EINVAL;
+ }
+
+ writel_relaxed(I3C_SINT_DACHG, svc->regs + I3C_SINTSET);
+ ret = wait_for_completion_timeout(&svc->dacomplete, msecs_to_jiffies(100));
+ writel_relaxed(I3C_SINT_DACHG, svc->regs + I3C_SINTCLR);
+ if (!ret) {
+ dev_err(&ctrl->dev, "wait for da assignment timeout\n");
+ return -EIO;
+ }
+
+ val = readl_relaxed(svc->regs + I3C_SMAPCTRL0);
+ val = FIELD_GET(I3C_SMAPCTRL0_DA_MASK, val);
+ dev_info(&ctrl->dev, "Get dynamtic address 0x%x\n", val);
+ return 0;
+}
+
+static int svc_i3c_set_status_format1(struct i3c_target_ctrl *ctrl, u16 status)
+{
+ struct svc_i3c_target *svc;
+ unsigned long flags;
+ u32 val;
+
+ svc = dev_get_drvdata(&ctrl->dev);
+
+ spin_lock_irqsave(&svc->ctrl_lock, flags);
+ val = readl_relaxed(svc->regs + I3C_SCTRL);
+ val &= 0xFFFF;
+ val |= status << 16;
+ writel_relaxed(val, svc->regs + I3C_SCTRL);
+ spin_unlock_irqrestore(&svc->ctrl_lock, flags);
+
+ return 0;
+}
+
+static u16 svc_i3c_get_status_format1(struct i3c_target_ctrl *ctrl)
+{
+ struct svc_i3c_target *svc;
+
+ svc = dev_get_drvdata(&ctrl->dev);
+
+ return readl_relaxed(svc->regs + I3C_SCTRL) >> 16;
+}
+
+static u8 svc_i3c_get_addr(struct i3c_target_ctrl *ctrl)
+{
+ struct svc_i3c_target *svc;
+ int val;
+
+ svc = dev_get_drvdata(&ctrl->dev);
+
+ val = readl_relaxed(svc->regs + I3C_SMAPCTRL0);
+
+ if (val & I3C_SMAPCTRL0_ENA_MASK)
+ return FIELD_GET(I3C_SMAPCTRL0_DA_MASK, val);
+
+ return 0;
+}
+
+static int svc_i3c_fifo_status(struct i3c_target_ctrl *ctrl, bool tx)
+{
+ struct svc_i3c_target *svc;
+ int val;
+
+ svc = dev_get_drvdata(&ctrl->dev);
+
+ val = readl_relaxed(svc->regs + I3C_SDATACTRL);
+
+ if (tx)
+ return FIELD_GET(I3C_SDATACTRL_TXCOUNT_MASK, val);
+ else
+ return FIELD_GET(I3C_SDATACTRL_RXCOUNT_MASK, val);
+}
+
+static struct i3c_target_ctrl_ops svc_i3c_target_ops = {
+ .set_config = svc_i3c_target_set_config,
+ .enable = svc_i3c_target_enable,
+ .disable = svc_i3c_target_disable,
+ .queue = svc_i3c_target_queue,
+ .dequeue = svc_i3c_dequeue,
+ .raise_ibi = svc_i3c_target_raise_ibi,
+ .fifo_flush = svc_i3c_target_fifo_flush,
+ .cancel_all_reqs = svc_i3c_cancel_all_reqs,
+ .get_features = svc_i3c_get_features,
+ .hotjoin = svc_i3c_hotjoin,
+ .fifo_status = svc_i3c_fifo_status,
+ .set_status_format1 = svc_i3c_set_status_format1,
+ .get_status_format1 = svc_i3c_get_status_format1,
+ .get_addr = svc_i3c_get_addr,
+};
+
+int svc_i3c_target_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct i3c_target_ctrl *target;
+ struct svc_i3c_target *svc;
+ int ret;
+ u32 val;
+
+ svc = devm_kzalloc(dev, sizeof(*svc), GFP_KERNEL);
+ if (!svc)
+ return -ENOMEM;
+
+ svc->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(svc->regs))
+ return PTR_ERR(svc->regs);
+
+ svc->clks[PCLK].id = "pclk";
+ svc->clks[FCLK].id = "fast_clk";
+ svc->clks[SCLK].id = "slow_clk";
+
+ ret = devm_clk_bulk_get(dev, MAXCLK, svc->clks);
+ if (ret < 0) {
+ dev_err(dev, "fail get clks: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_bulk_prepare_enable(MAXCLK, svc->clks);
+ if (ret < 0) {
+ dev_err(dev, "fail enable clks: %d\n", ret);
+ return ret;
+ }
+
+ svc->irq = platform_get_irq(pdev, 0);
+ if (svc->irq < 0)
+ return svc->irq;
+
+ INIT_LIST_HEAD(&svc->txq);
+ INIT_LIST_HEAD(&svc->rxq);
+ INIT_LIST_HEAD(&svc->cq);
+ spin_lock_init(&svc->txq_lock);
+ spin_lock_init(&svc->rxq_lock);
+ spin_lock_init(&svc->cq_lock);
+ spin_lock_init(&svc->ctrl_lock);
+
+ init_completion(&svc->dacomplete);
+
+ INIT_WORK(&svc->work, svc_i3c_target_complete);
+ svc->workqueue = alloc_workqueue("%s-cq", 0, 0, dev_name(dev));
+ if (!svc->workqueue)
+ return -ENOMEM;
+
+ /* Disable all IRQ */
+ writel_relaxed(0xFFFFFFFF, svc->regs + I3C_SINTCLR);
+
+ val = readl_relaxed(svc->regs + I3C_SCAPABILITIES);
+ svc->features.tx_fifo_sz = FIELD_GET(I3C_SCAPABILITIES_FIFOTX_MASK, val);
+ svc->features.tx_fifo_sz = 2 << svc->features.tx_fifo_sz;
+
+ svc->features.rx_fifo_sz = FIELD_GET(I3C_SCAPABILITIES_FIFORX_MASK, val);
+ svc->features.rx_fifo_sz = 2 << svc->features.rx_fifo_sz;
+
+ ret = devm_request_irq(dev, svc->irq, svc_i3c_target_irq_handler, 0, "svc-i3c-irq", svc);
+ if (ret)
+ return -ENOENT;
+
+ target = devm_i3c_target_ctrl_create(dev, &svc_i3c_target_ops);
+ if (!target)
+ return -ENOMEM;
+
+ dev_set_drvdata(&target->dev, svc);
+
+ return 0;
+}
+
+void svc_i3c_target_remove(struct platform_device *pdev)
+{
+}
diff --git a/drivers/i3c/master/svc-i3c.h b/drivers/i3c/master/svc-i3c.h
index b9e1dce4b2109..1bb77474f36eb 100644
--- a/drivers/i3c/master/svc-i3c.h
+++ b/drivers/i3c/master/svc-i3c.h
@@ -8,5 +8,8 @@ void svc_i3c_master_remove(struct platform_device *pdev);
int svc_i3c_master_runtime_suspend(struct device *dev);
int svc_i3c_master_runtime_resume(struct device *dev);
+int svc_i3c_target_probe(struct platform_device *pdev);
+void svc_i3c_target_remove(struct platform_device *pdev);
+
#endif
--
2.34.1
Add tty over I3C target function driver.
Signed-off-by: Frank Li <[email protected]>
---
Notes:
Change from v4 to v5
- remove void*
- include bitfield.h
- remove extra ()
- oneline for struct ttyi3c_port *sport
drivers/i3c/Kconfig | 3 +
drivers/i3c/Makefile | 1 +
drivers/i3c/func/Kconfig | 9 +
drivers/i3c/func/Makefile | 3 +
drivers/i3c/func/tty.c | 474 ++++++++++++++++++++++++++++++++++++++
5 files changed, 490 insertions(+)
create mode 100644 drivers/i3c/func/Kconfig
create mode 100644 drivers/i3c/func/Makefile
create mode 100644 drivers/i3c/func/tty.c
diff --git a/drivers/i3c/Kconfig b/drivers/i3c/Kconfig
index d59a7eb83d13a..fca808cda87b3 100644
--- a/drivers/i3c/Kconfig
+++ b/drivers/i3c/Kconfig
@@ -48,3 +48,6 @@ config I3C_TARGET_CONFIGFS
the target function and used to bind the function with a target
controller.
+if I3C_TARGET
+source "drivers/i3c/func/Kconfig"
+endif # I3C_TARGET
diff --git a/drivers/i3c/Makefile b/drivers/i3c/Makefile
index c275aeae8970c..11f026d6876fe 100644
--- a/drivers/i3c/Makefile
+++ b/drivers/i3c/Makefile
@@ -4,3 +4,4 @@ obj-$(CONFIG_I3C) += i3c.o
obj-$(CONFIG_I3C_TARGET) += target.o
obj-$(CONFIG_I3C_TARGET_CONFIGFS) += i3c-cfs.o
obj-$(CONFIG_I3C) += master/
+obj-$(CONFIG_I3C_TARGET) += func/
diff --git a/drivers/i3c/func/Kconfig b/drivers/i3c/func/Kconfig
new file mode 100644
index 0000000000000..7115129eb7d5a
--- /dev/null
+++ b/drivers/i3c/func/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config I3C_TARGET_FUNC_TTY
+ tristate "I3C target tty driver"
+ depends on I3C_TARGET
+ help
+ I3C Target TTY Function Driver.
+
+ General TTY over I3C target controller function drivers.
diff --git a/drivers/i3c/func/Makefile b/drivers/i3c/func/Makefile
new file mode 100644
index 0000000000000..16b3b9301496b
--- /dev/null
+++ b/drivers/i3c/func/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_I3C_TARGET_FUNC_TTY) += tty.o
diff --git a/drivers/i3c/func/tty.c b/drivers/i3c/func/tty.c
new file mode 100644
index 0000000000000..50673bfb6a003
--- /dev/null
+++ b/drivers/i3c/func/tty.c
@@ -0,0 +1,474 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2023 NXP
+ * Author: Frank Li <[email protected]>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/iopoll.h>
+#include <linux/i3c/target.h>
+#include <linux/serial_core.h>
+#include <linux/slab.h>
+#include <linux/tty_flip.h>
+
+static DEFINE_IDR(i3c_tty_minors);
+
+static struct tty_driver *i3c_tty_driver;
+
+#define I3C_TTY_MINORS 8
+
+#define I3C_TX_NOEMPTY BIT(0)
+#define I3C_TTY_TRANS_SIZE 16
+#define I3C_TTY_IBI_TX BIT(0)
+
+struct ttyi3c_port {
+ struct tty_port port;
+ int minor;
+ struct i3c_target_func *i3cdev;
+ struct completion txcomplete;
+ spinlock_t xlock;
+ void *buffer;
+ struct work_struct work;
+ u16 status;
+ struct i3c_request *req;
+};
+
+static void i3c_target_tty_rx_complete(struct i3c_request *req)
+{
+ struct ttyi3c_port *port = req->context;
+
+ if (req->status == I3C_REQUEST_CANCEL) {
+ i3c_target_ctrl_free_request(req);
+ return;
+ }
+
+ tty_insert_flip_string(&port->port, req->buf, req->actual);
+ tty_flip_buffer_push(&port->port);
+
+ req->actual = 0;
+ req->status = 0;
+ i3c_target_ctrl_queue(req, GFP_KERNEL);
+}
+
+static void i3c_target_tty_tx_complete(struct i3c_request *req)
+{
+ struct ttyi3c_port *sport = req->context;
+ unsigned long flags;
+
+ if (req->status == I3C_REQUEST_CANCEL) {
+ i3c_target_ctrl_free_request(req);
+ return;
+ }
+
+ spin_lock_irqsave(&sport->xlock, flags);
+ kfifo_dma_out_finish(&sport->port.xmit_fifo, req->actual);
+ sport->req = NULL;
+
+ if (kfifo_is_empty(&sport->port.xmit_fifo))
+ complete(&sport->txcomplete);
+ else
+ queue_work(system_unbound_wq, &sport->work);
+
+ if (kfifo_len(&sport->port.xmit_fifo) < WAKEUP_CHARS)
+ tty_port_tty_wakeup(&sport->port);
+ spin_unlock_irqrestore(&sport->xlock, flags);
+
+ i3c_target_ctrl_free_request(req);
+}
+
+static void i3c_target_tty_i3c_work(struct work_struct *work)
+{
+ struct ttyi3c_port *sport = container_of(work, struct ttyi3c_port, work);
+ struct i3c_request *req = sport->req;
+ struct scatterlist sg[1];
+ unsigned int nents;
+ u8 ibi;
+
+ if (kfifo_is_empty(&sport->port.xmit_fifo))
+ return;
+
+ if (!req) {
+ req = i3c_target_ctrl_alloc_request(sport->i3cdev->ctrl, GFP_KERNEL);
+ if (!req)
+ return;
+
+ sg_init_table(sg, ARRAY_SIZE(sg));
+ nents = kfifo_dma_out_prepare(&sport->port.xmit_fifo, sg, ARRAY_SIZE(sg),
+ UART_XMIT_SIZE);
+ if (!nents)
+ goto err;
+
+ req->length = sg->length;
+ req->buf = sg_virt(sg);
+
+ req->complete = i3c_target_tty_tx_complete;
+ req->context = sport;
+ req->tx = true;
+
+ if (i3c_target_ctrl_queue(req, GFP_KERNEL))
+ goto err;
+
+ sport->req = req;
+ }
+
+ ibi = I3C_TTY_IBI_TX;
+ i3c_target_ctrl_raise_ibi(sport->i3cdev->ctrl, &ibi, 1);
+
+ return;
+
+err:
+ i3c_target_ctrl_free_request(req);
+}
+
+static int i3c_port_activate(struct tty_port *port, struct tty_struct *tty)
+{
+ struct ttyi3c_port *sport = container_of(port, struct ttyi3c_port, port);
+ const struct i3c_target_ctrl_features *feature;
+ struct i3c_target_func *func = sport->i3cdev;
+ struct i3c_request *req;
+ int rxfifo_size;
+ int offset = 0;
+ int ret;
+
+ feature = i3c_target_ctrl_get_features(func->ctrl);
+ if (!feature)
+ return -EINVAL;
+
+ ret = tty_port_alloc_xmit_buf(port);
+ if (ret)
+ return ret;
+
+ sport->buffer = (void *)get_zeroed_page(GFP_KERNEL);
+ if (!sport->buffer)
+ goto err_alloc_rx_buf;
+
+ rxfifo_size = feature->rx_fifo_sz;
+
+ if (!rxfifo_size)
+ rxfifo_size = I3C_TTY_TRANS_SIZE;
+
+ do {
+ req = i3c_target_ctrl_alloc_request(func->ctrl, GFP_KERNEL);
+ if (!req)
+ goto err_alloc_req;
+
+ req->buf = sport->buffer + offset;
+ req->length = rxfifo_size;
+ req->context = sport;
+ req->complete = i3c_target_tty_rx_complete;
+ offset += rxfifo_size;
+
+ if (i3c_target_ctrl_queue(req, GFP_KERNEL))
+ goto err_alloc_req;
+ } while (req && offset + rxfifo_size < UART_XMIT_SIZE);
+
+ reinit_completion(&sport->txcomplete);
+
+ return 0;
+
+err_alloc_req:
+ i3c_target_ctrl_cancel_all_reqs(func->ctrl, false);
+ free_page((unsigned long)sport->buffer);
+err_alloc_rx_buf:
+ tty_port_free_xmit_buf(port);
+ return -ENOMEM;
+}
+
+static void i3c_port_shutdown(struct tty_port *port)
+{
+ struct ttyi3c_port *sport = container_of(port, struct ttyi3c_port, port);
+
+ cancel_work_sync(&sport->work);
+
+ i3c_target_ctrl_cancel_all_reqs(sport->i3cdev->ctrl, true);
+ i3c_target_ctrl_cancel_all_reqs(sport->i3cdev->ctrl, false);
+
+ i3c_target_ctrl_fifo_flush(sport->i3cdev->ctrl, true);
+ i3c_target_ctrl_fifo_flush(sport->i3cdev->ctrl, false);
+
+ tty_port_free_xmit_buf(port);
+ free_page((unsigned long)sport->buffer);
+}
+
+static void i3c_port_destruct(struct tty_port *port)
+{
+ struct ttyi3c_port *sport = container_of(port, struct ttyi3c_port, port);
+
+ idr_remove(&i3c_tty_minors, sport->minor);
+}
+
+static const struct tty_port_operations i3c_port_ops = {
+ .shutdown = i3c_port_shutdown,
+ .activate = i3c_port_activate,
+ .destruct = i3c_port_destruct,
+};
+
+static int i3c_target_tty_bind(struct i3c_target_func *func)
+{
+ struct ttyi3c_port *sport;
+ struct device *tty_dev;
+ int minor;
+ int ret;
+
+ sport = dev_get_drvdata(&func->dev);
+
+ if (i3c_target_ctrl_set_config(func->ctrl, func)) {
+ dev_err(&func->dev, "failed to set i3c config\n");
+ return -EINVAL;
+ }
+
+ spin_lock_init(&sport->xlock);
+ init_completion(&sport->txcomplete);
+
+ ret = minor = idr_alloc(&i3c_tty_minors, sport, 0, I3C_TTY_MINORS, GFP_KERNEL);
+
+ if (minor < 0)
+ goto err_idr_alloc;
+
+ tty_port_init(&sport->port);
+ sport->port.ops = &i3c_port_ops;
+
+ tty_dev = tty_port_register_device(&sport->port, i3c_tty_driver, minor,
+ &func->dev);
+ if (IS_ERR(tty_dev)) {
+ ret = PTR_ERR(tty_dev);
+ goto err_register_port;
+ }
+
+ sport->minor = minor;
+ ret = i3c_target_ctrl_enable(func->ctrl);
+ if (ret)
+ goto err_ctrl_enable;
+
+ return 0;
+
+err_ctrl_enable:
+ tty_port_unregister_device(&sport->port, i3c_tty_driver, sport->minor);
+err_register_port:
+ idr_remove(&i3c_tty_minors, sport->minor);
+err_idr_alloc:
+ i3c_target_ctrl_cancel_all_reqs(func->ctrl, false);
+ dev_err(&func->dev, "bind failure\n");
+
+ return ret;
+}
+
+static void i3c_target_tty_unbind(struct i3c_target_func *func)
+{
+ struct ttyi3c_port *sport;
+
+ sport = dev_get_drvdata(&func->dev);
+
+ cancel_work_sync(&sport->work);
+
+ i3c_target_ctrl_disable(func->ctrl);
+ i3c_target_ctrl_cancel_all_reqs(func->ctrl, 0);
+ i3c_target_ctrl_cancel_all_reqs(func->ctrl, 1);
+
+ tty_port_unregister_device(&sport->port, i3c_tty_driver, sport->minor);
+
+ free_page((unsigned long)sport->buffer);
+}
+
+static struct i3c_target_func_ops i3c_func_ops = {
+ .bind = i3c_target_tty_bind,
+ .unbind = i3c_target_tty_unbind,
+};
+
+static int i3c_tty_probe(struct i3c_target_func *func)
+{
+ struct device *dev = &func->dev;
+ struct ttyi3c_port *port;
+
+ port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
+ if (!port)
+ return -ENOMEM;
+
+ port->i3cdev = func;
+ dev_set_drvdata(&func->dev, port);
+
+ INIT_WORK(&port->work, i3c_target_tty_i3c_work);
+
+ return 0;
+}
+
+static ssize_t i3c_write(struct tty_struct *tty, const unsigned char *buf, size_t count)
+{
+ struct ttyi3c_port *sport = tty->driver_data;
+ unsigned long flags;
+ bool is_empty;
+ int ret = 0;
+
+ spin_lock_irqsave(&sport->xlock, flags);
+ ret = kfifo_in(&sport->port.xmit_fifo, buf, count);
+ is_empty = kfifo_is_empty(&sport->port.xmit_fifo);
+ i3c_target_ctrl_set_status_format1(sport->i3cdev->ctrl, sport->status | I3C_TX_NOEMPTY);
+ spin_unlock_irqrestore(&sport->xlock, flags);
+
+ if (!is_empty)
+ queue_work(system_unbound_wq, &sport->work);
+
+ return ret;
+}
+
+static int i3c_put_char(struct tty_struct *tty, unsigned char ch)
+{
+ struct ttyi3c_port *sport = tty->driver_data;
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&sport->xlock, flags);
+ ret = kfifo_put(&sport->port.xmit_fifo, ch);
+ spin_unlock_irqrestore(&sport->xlock, flags);
+
+ return ret;
+}
+
+static void i3c_flush_chars(struct tty_struct *tty)
+{
+ struct ttyi3c_port *sport = tty->driver_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sport->xlock, flags);
+ if (!kfifo_is_empty(&sport->port.xmit_fifo))
+ queue_work(system_unbound_wq, &sport->work);
+ spin_unlock_irqrestore(&sport->xlock, flags);
+}
+
+static unsigned int i3c_write_room(struct tty_struct *tty)
+{
+ struct ttyi3c_port *sport = tty->driver_data;
+
+ return kfifo_avail(&sport->port.xmit_fifo);
+}
+
+static void i3c_throttle(struct tty_struct *tty)
+{
+ struct ttyi3c_port *sport = tty->driver_data;
+
+ i3c_target_ctrl_cancel_all_reqs(sport->i3cdev->ctrl, false);
+}
+
+static void i3c_unthrottle(struct tty_struct *tty)
+{
+ struct ttyi3c_port *sport = tty->driver_data;
+
+ i3c_port_activate(&sport->port, tty);
+}
+
+static int i3c_open(struct tty_struct *tty, struct file *filp)
+{
+ struct ttyi3c_port *sport = container_of(tty->port, struct ttyi3c_port, port);
+ int ret;
+
+ tty->driver_data = sport;
+
+ if (!i3c_target_ctrl_get_addr(sport->i3cdev->ctrl)) {
+ dev_dbg(&sport->i3cdev->dev, "No target addr assigned, try hotjoin");
+ ret = i3c_target_ctrl_hotjoin(sport->i3cdev->ctrl);
+ if (ret) {
+ dev_err(&sport->i3cdev->dev, "Hotjoin failure, check connection");
+ return ret;
+ }
+ }
+
+ return tty_port_open(&sport->port, tty, filp);
+}
+
+static void i3c_close(struct tty_struct *tty, struct file *filp)
+{
+ tty_port_close(tty->port, tty, filp);
+}
+
+static void i3c_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+ struct ttyi3c_port *sport = tty->driver_data;
+ int val;
+ int ret;
+ u8 ibi = I3C_TTY_IBI_TX;
+ int retry = 100;
+
+ if (!kfifo_is_empty(&sport->port.xmit_fifo)) {
+ do {
+ ret = wait_for_completion_timeout(&sport->txcomplete, timeout / 100);
+ if (ret)
+ break;
+ i3c_target_ctrl_raise_ibi(sport->i3cdev->ctrl, &ibi, 1);
+ } while (retry--);
+
+ reinit_completion(&sport->txcomplete);
+ }
+
+ read_poll_timeout(i3c_target_ctrl_fifo_status, val, !val, 100, timeout, false,
+ sport->i3cdev->ctrl, true);
+
+ i3c_target_ctrl_set_status_format1(sport->i3cdev->ctrl, sport->status & ~I3C_TX_NOEMPTY);
+}
+
+static const struct tty_operations i3c_tty_ops = {
+ .open = i3c_open,
+ .close = i3c_close,
+ .write = i3c_write,
+ .put_char = i3c_put_char,
+ .flush_chars = i3c_flush_chars,
+ .write_room = i3c_write_room,
+ .throttle = i3c_throttle,
+ .unthrottle = i3c_unthrottle,
+ .wait_until_sent = i3c_wait_until_sent,
+};
+
+DECLARE_I3C_TARGET_FUNC(tty, i3c_tty_probe, NULL, &i3c_func_ops);
+
+static int __init i3c_tty_init(void)
+{
+ int ret;
+
+ i3c_tty_driver = tty_alloc_driver(
+ I3C_TTY_MINORS, TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV);
+
+ if (IS_ERR(i3c_tty_driver))
+ return PTR_ERR(i3c_tty_driver);
+
+ i3c_tty_driver->driver_name = "ttySI3C", i3c_tty_driver->name = "ttySI3C",
+ i3c_tty_driver->minor_start = 0,
+ i3c_tty_driver->type = TTY_DRIVER_TYPE_SERIAL,
+ i3c_tty_driver->subtype = SERIAL_TYPE_NORMAL,
+ i3c_tty_driver->init_termios = tty_std_termios;
+ i3c_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL |
+ CLOCAL;
+ i3c_tty_driver->init_termios.c_lflag = 0;
+
+ tty_set_operations(i3c_tty_driver, &i3c_tty_ops);
+
+ ret = tty_register_driver(i3c_tty_driver);
+ if (ret)
+ goto err_register_tty_driver;
+
+ ret = i3c_target_func_register_driver(&ttyi3c_func);
+ if (ret)
+ goto err_register_i3c_driver;
+
+ return 0;
+
+err_register_i3c_driver:
+ tty_unregister_driver(i3c_tty_driver);
+
+err_register_tty_driver:
+ tty_driver_kref_put(i3c_tty_driver);
+
+ return ret;
+}
+
+static void __exit i3c_tty_exit(void)
+{
+ i3c_target_func_unregister_driver(&ttyi3c_func);
+ tty_unregister_driver(i3c_tty_driver);
+ tty_driver_kref_put(i3c_tty_driver);
+ idr_destroy(&i3c_tty_minors);
+}
+
+module_init(i3c_tty_init);
+module_exit(i3c_tty_exit);
+
+MODULE_LICENSE("GPL");
+
--
2.34.1
On Mon, 5 Feb 2024, Frank Li wrote:
> Add tty over I3C target function driver.
>
> Signed-off-by: Frank Li <[email protected]>
> ---
>
> Notes:
> Change from v4 to v5
> - remove void*
> - include bitfield.h
> - remove extra ()
> - oneline for struct ttyi3c_port *sport
>
> drivers/i3c/Kconfig | 3 +
> drivers/i3c/Makefile | 1 +
> drivers/i3c/func/Kconfig | 9 +
> drivers/i3c/func/Makefile | 3 +
> drivers/i3c/func/tty.c | 474 ++++++++++++++++++++++++++++++++++++++
> 5 files changed, 490 insertions(+)
> create mode 100644 drivers/i3c/func/Kconfig
> create mode 100644 drivers/i3c/func/Makefile
> create mode 100644 drivers/i3c/func/tty.c
>
> diff --git a/drivers/i3c/Kconfig b/drivers/i3c/Kconfig
> index d59a7eb83d13a..fca808cda87b3 100644
> --- a/drivers/i3c/Kconfig
> +++ b/drivers/i3c/Kconfig
> @@ -48,3 +48,6 @@ config I3C_TARGET_CONFIGFS
> the target function and used to bind the function with a target
> controller.
>
> +if I3C_TARGET
> +source "drivers/i3c/func/Kconfig"
> +endif # I3C_TARGET
> diff --git a/drivers/i3c/Makefile b/drivers/i3c/Makefile
> index c275aeae8970c..11f026d6876fe 100644
> --- a/drivers/i3c/Makefile
> +++ b/drivers/i3c/Makefile
> @@ -4,3 +4,4 @@ obj-$(CONFIG_I3C) += i3c.o
> obj-$(CONFIG_I3C_TARGET) += target.o
> obj-$(CONFIG_I3C_TARGET_CONFIGFS) += i3c-cfs.o
> obj-$(CONFIG_I3C) += master/
> +obj-$(CONFIG_I3C_TARGET) += func/
> diff --git a/drivers/i3c/func/Kconfig b/drivers/i3c/func/Kconfig
> new file mode 100644
> index 0000000000000..7115129eb7d5a
> --- /dev/null
> +++ b/drivers/i3c/func/Kconfig
> @@ -0,0 +1,9 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +config I3C_TARGET_FUNC_TTY
> + tristate "I3C target tty driver"
> + depends on I3C_TARGET
> + help
> + I3C Target TTY Function Driver.
> +
> + General TTY over I3C target controller function drivers.
> diff --git a/drivers/i3c/func/Makefile b/drivers/i3c/func/Makefile
> new file mode 100644
> index 0000000000000..16b3b9301496b
> --- /dev/null
> +++ b/drivers/i3c/func/Makefile
> @@ -0,0 +1,3 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +obj-$(CONFIG_I3C_TARGET_FUNC_TTY) += tty.o
> diff --git a/drivers/i3c/func/tty.c b/drivers/i3c/func/tty.c
> new file mode 100644
> index 0000000000000..50673bfb6a003
> --- /dev/null
> +++ b/drivers/i3c/func/tty.c
> @@ -0,0 +1,474 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2023 NXP
> + * Author: Frank Li <[email protected]>
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/iopoll.h>
> +#include <linux/i3c/target.h>
> +#include <linux/serial_core.h>
> +#include <linux/slab.h>
> +#include <linux/tty_flip.h>
> +
> +static DEFINE_IDR(i3c_tty_minors);
> +
> +static struct tty_driver *i3c_tty_driver;
> +
> +#define I3C_TTY_MINORS 8
> +
> +#define I3C_TX_NOEMPTY BIT(0)
> +#define I3C_TTY_TRANS_SIZE 16
> +#define I3C_TTY_IBI_TX BIT(0)
This is #include <linux/bits.h>
..which will include <vdso/bits.h> that contains the actual definition.
#include <bitfield.h> is for FIELD_GET/PREP(), etc.
> +struct ttyi3c_port {
> + struct tty_port port;
> + int minor;
> + struct i3c_target_func *i3cdev;
> + struct completion txcomplete;
> + spinlock_t xlock;
> + void *buffer;
> + struct work_struct work;
This file seems to also lack some includes. Please go through your
#include in the series and add those you use.
--
i.
On Tue, Feb 06, 2024 at 01:54:42PM +0200, Ilpo J?rvinen wrote:
> On Mon, 5 Feb 2024, Frank Li wrote:
>
> > Add tty over I3C target function driver.
> >
> > Signed-off-by: Frank Li <[email protected]>
> > ---
> >
> > Notes:
> > Change from v4 to v5
> > - remove void*
> > - include bitfield.h
> > - remove extra ()
> > - oneline for struct ttyi3c_port *sport
> >
> > drivers/i3c/Kconfig | 3 +
> > drivers/i3c/Makefile | 1 +
> > drivers/i3c/func/Kconfig | 9 +
> > drivers/i3c/func/Makefile | 3 +
> > drivers/i3c/func/tty.c | 474 ++++++++++++++++++++++++++++++++++++++
> > 5 files changed, 490 insertions(+)
> > create mode 100644 drivers/i3c/func/Kconfig
> > create mode 100644 drivers/i3c/func/Makefile
> > create mode 100644 drivers/i3c/func/tty.c
> >
> > diff --git a/drivers/i3c/Kconfig b/drivers/i3c/Kconfig
> > index d59a7eb83d13a..fca808cda87b3 100644
> > --- a/drivers/i3c/Kconfig
> > +++ b/drivers/i3c/Kconfig
> > @@ -48,3 +48,6 @@ config I3C_TARGET_CONFIGFS
> > the target function and used to bind the function with a target
> > controller.
> >
> > +if I3C_TARGET
> > +source "drivers/i3c/func/Kconfig"
> > +endif # I3C_TARGET
> > diff --git a/drivers/i3c/Makefile b/drivers/i3c/Makefile
> > index c275aeae8970c..11f026d6876fe 100644
> > --- a/drivers/i3c/Makefile
> > +++ b/drivers/i3c/Makefile
> > @@ -4,3 +4,4 @@ obj-$(CONFIG_I3C) += i3c.o
> > obj-$(CONFIG_I3C_TARGET) += target.o
> > obj-$(CONFIG_I3C_TARGET_CONFIGFS) += i3c-cfs.o
> > obj-$(CONFIG_I3C) += master/
> > +obj-$(CONFIG_I3C_TARGET) += func/
> > diff --git a/drivers/i3c/func/Kconfig b/drivers/i3c/func/Kconfig
> > new file mode 100644
> > index 0000000000000..7115129eb7d5a
> > --- /dev/null
> > +++ b/drivers/i3c/func/Kconfig
> > @@ -0,0 +1,9 @@
> > +# SPDX-License-Identifier: GPL-2.0
> > +
> > +config I3C_TARGET_FUNC_TTY
> > + tristate "I3C target tty driver"
> > + depends on I3C_TARGET
> > + help
> > + I3C Target TTY Function Driver.
> > +
> > + General TTY over I3C target controller function drivers.
> > diff --git a/drivers/i3c/func/Makefile b/drivers/i3c/func/Makefile
> > new file mode 100644
> > index 0000000000000..16b3b9301496b
> > --- /dev/null
> > +++ b/drivers/i3c/func/Makefile
> > @@ -0,0 +1,3 @@
> > +# SPDX-License-Identifier: GPL-2.0
> > +
> > +obj-$(CONFIG_I3C_TARGET_FUNC_TTY) += tty.o
> > diff --git a/drivers/i3c/func/tty.c b/drivers/i3c/func/tty.c
> > new file mode 100644
> > index 0000000000000..50673bfb6a003
> > --- /dev/null
> > +++ b/drivers/i3c/func/tty.c
> > @@ -0,0 +1,474 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (C) 2023 NXP
> > + * Author: Frank Li <[email protected]>
> > + */
> > +
> > +#include <linux/bitfield.h>
> > +#include <linux/iopoll.h>
> > +#include <linux/i3c/target.h>
> > +#include <linux/serial_core.h>
> > +#include <linux/slab.h>
> > +#include <linux/tty_flip.h>
> > +
> > +static DEFINE_IDR(i3c_tty_minors);
> > +
> > +static struct tty_driver *i3c_tty_driver;
> > +
> > +#define I3C_TTY_MINORS 8
> > +
> > +#define I3C_TX_NOEMPTY BIT(0)
> > +#define I3C_TTY_TRANS_SIZE 16
> > +#define I3C_TTY_IBI_TX BIT(0)
>
> This is #include <linux/bits.h>
>
> ...which will include <vdso/bits.h> that contains the actual definition.
>
> #include <bitfield.h> is for FIELD_GET/PREP(), etc.
>
> > +struct ttyi3c_port {
> > + struct tty_port port;
> > + int minor;
> > + struct i3c_target_func *i3cdev;
> > + struct completion txcomplete;
> > + spinlock_t xlock;
> > + void *buffer;
> > + struct work_struct work;
>
> This file seems to also lack some includes. Please go through your
> #include in the series and add those you use.
Do you have tools to check? It is easy to missed some header files.
Frank
>
> --
> i.
>
Hi Frank,
> -----Original Message-----
> From: linux-i3c <[email protected]> On Behalf Of Frank Li
> Sent: Tuesday, February 6, 2024 7:33 AM
> To: [email protected]
> Cc: [email protected]; [email protected];
> [email protected]; [email protected];
> [email protected]; [email protected]; [email protected];
> [email protected]; [email protected];
> [email protected]; [email protected]; linux-
> [email protected]; [email protected];
> [email protected]; [email protected];
> [email protected]
> Subject: [PATCH v7 5/8] i3c: target: add svc target controller support
>
> Add Silvaco I3C target controller support
>
> Signed-off-by: Frank Li <[email protected]>
> ---
>
> Notes:
> Change from v2 to v3
> - fix build warning
>
> drivers/i3c/master/Makefile | 2 +-
> drivers/i3c/master/svc-i3c-main.c | 35 +-
> drivers/i3c/master/svc-i3c-target.c | 776
I think putting target mode files under "master" might not make sense. We might have to
consider that we may have a "secondary master" mode. Some other ways of splitting
or handling of target mode is needed here.
..
> +
> +#define I3C_SCONFIG 0x4
> +#define I3C_SCONFIG_SLVENA_MASK BIT(0)
> +#define I3C_SCONFIG_OFFLINE_MASK BIT(9)
> +#define I3C_SCONFIG_SADDR_MASK GENMASK(31, 25)
> +
> +#define I3C_SSTATUS 0x8
> +#define I3C_SSTATUS_STNOTSTOP_MASK BIT(0)
> +#define I3C_SSTATUS_STOP_MASK BIT(10)
> +#define I3C_SSTATUS_RX_PEND_MASK BIT(11)
> +#define I3C_SSTATUS_TXNOTFULL_MASK BIT(12)
> +#define I3C_SSTATUS_DACHG_MASK BIT(13)
> +#define I3C_SSTATUS_EVDET_MASK GENMASK(21, 20)
> +#define I3C_SSTATUS_EVDET_ACKED 0x3
> +#define I3C_SSTATUS_IBIDIS_MASK BIT(24)
> +#define I3C_SSTATUS_HJDIS_MASK BIT(27)
> +
There is couple of space formatting here that requires to be fixed.
Cheers,
Joshua
On Wed, Mar 06, 2024 at 04:01:17PM +0000, Joshua Yeong wrote:
> Hi Frank,
>
> > -----Original Message-----
> > From: linux-i3c <[email protected]> On Behalf Of Frank Li
> > Sent: Tuesday, February 6, 2024 7:33 AM
> > To: [email protected]
> > Cc: [email protected]; [email protected];
> > [email protected]; [email protected];
> > [email protected]; [email protected]; [email protected];
> > [email protected]; [email protected];
> > [email protected]; [email protected]; linux-
> > [email protected]; [email protected];
> > [email protected]; [email protected];
> > [email protected]
> > Subject: [PATCH v7 5/8] i3c: target: add svc target controller support
> >
> > Add Silvaco I3C target controller support
> >
> > Signed-off-by: Frank Li <[email protected]>
> > ---
> >
> > Notes:
> > Change from v2 to v3
> > - fix build warning
> >
> > drivers/i3c/master/Makefile | 2 +-
> > drivers/i3c/master/svc-i3c-main.c | 35 +-
> > drivers/i3c/master/svc-i3c-target.c | 776
>
> I think putting target mode files under "master" might not make sense. We might have to
> consider that we may have a "secondary master" mode. Some other ways of splitting
> or handling of target mode is needed here.
I think name 'master' is not good here. Previously only support 'master'
mode, it should be fine. Now many controller are dual mode.
And I3C spec use 'controller/target' instead of 'master/slave'. I think
'controller' as master are quite confused. It can be master controller
and slave controller.
Anyway, slave/master may share some code and resource, even only one file.
So far, I think it is fine put under master now. we can rename 'master'
later when more dual mode controller added.
Frank
>
> ...
>
> > +
> > +#define I3C_SCONFIG 0x4
> > +#define I3C_SCONFIG_SLVENA_MASK BIT(0)
> > +#define I3C_SCONFIG_OFFLINE_MASK BIT(9)
> > +#define I3C_SCONFIG_SADDR_MASK GENMASK(31, 25)
> > +
> > +#define I3C_SSTATUS 0x8
> > +#define I3C_SSTATUS_STNOTSTOP_MASK BIT(0)
> > +#define I3C_SSTATUS_STOP_MASK BIT(10)
> > +#define I3C_SSTATUS_RX_PEND_MASK BIT(11)
> > +#define I3C_SSTATUS_TXNOTFULL_MASK BIT(12)
> > +#define I3C_SSTATUS_DACHG_MASK BIT(13)
> > +#define I3C_SSTATUS_EVDET_MASK GENMASK(21, 20)
> > +#define I3C_SSTATUS_EVDET_ACKED 0x3
> > +#define I3C_SSTATUS_IBIDIS_MASK BIT(24)
> > +#define I3C_SSTATUS_HJDIS_MASK BIT(27)
> > +
>
> There is couple of space formatting here that requires to be fixed.
>
> Cheers,
> Joshua
> -----Original Message-----
> From: Frank Li <[email protected]>
> Sent: Thursday, March 7, 2024 1:45 AM
> To: Joshua Yeong <[email protected]>
> Cc: [email protected]; [email protected];
> [email protected]; [email protected];
> [email protected]; [email protected]; [email protected];
> [email protected]; [email protected];
> [email protected]; [email protected]; linux-
> [email protected]; [email protected];
> [email protected]; [email protected];
> [email protected]
> Subject: Re: [PATCH v7 5/8] i3c: target: add svc target controller support
>
> On Wed, Mar 06, 2024 at 04:01:17PM +0000, Joshua Yeong wrote:
> > Hi Frank,
> >
> > > -----Original Message-----
> > > From: linux-i3c <[email protected]> On Behalf Of
> > > Frank Li
> > > Sent: Tuesday, February 6, 2024 7:33 AM
> > > To: [email protected]
> > > Cc: [email protected]; [email protected];
> > > [email protected]; [email protected];
> > > [email protected]; [email protected];
> > > [email protected]; [email protected];
> > > [email protected];
> > > [email protected]; [email protected];
> > > linux- [email protected]; [email protected];
> > > [email protected]; [email protected];
> > > [email protected]
> > > Subject: [PATCH v7 5/8] i3c: target: add svc target controller
> > > support
> > >
> > > Add Silvaco I3C target controller support
> > >
> > > Signed-off-by: Frank Li <[email protected]>
> > > ---
> > >
> > > Notes:
> > > Change from v2 to v3
> > > - fix build warning
> > >
> > > drivers/i3c/master/Makefile | 2 +-
> > > drivers/i3c/master/svc-i3c-main.c | 35 +-
> > > drivers/i3c/master/svc-i3c-target.c | 776
> >
> > I think putting target mode files under "master" might not make sense.
> > We might have to consider that we may have a "secondary master" mode.
> > Some other ways of splitting or handling of target mode is needed here.
>
> I think name 'master' is not good here. Previously only support 'master'
> mode, it should be fine. Now many controller are dual mode.
>
> And I3C spec use 'controller/target' instead of 'master/slave'. I think
> 'controller' as master are quite confused. It can be master controller and slave
> controller.
>
> Anyway, slave/master may share some code and resource, even only one file.
>
> So far, I think it is fine put under master now. we can rename 'master'
> later when more dual mode controller added.
>
> Frank
I am currently working on secondary-controller mode. I would like to explore if
we can have a different way to implement various modes.
I am guessing that the current inspiration is coming from the existing i2c
framework.
However, I am thinking of having a more generic function to replace the way how
we register the i3c driver. (existing 'i3c_master_register' or
'devm_i3c_target_ctrl_create' from yours) Maybe along the line where we can have
all the modes set in a struct and return to the i3c framework. So we only need 1
API to register all the different modes instead of having one each.
>
> >
> > ...
> >
> > > +
> > > +#define I3C_SCONFIG 0x4
> > > +#define I3C_SCONFIG_SLVENA_MASK BIT(0)
> > > +#define I3C_SCONFIG_OFFLINE_MASK BIT(9)
> > > +#define I3C_SCONFIG_SADDR_MASK GENMASK(31, 25)
> > > +
> > > +#define I3C_SSTATUS 0x8
> > > +#define I3C_SSTATUS_STNOTSTOP_MASK BIT(0)
> > > +#define I3C_SSTATUS_STOP_MASK BIT(10)
> > > +#define I3C_SSTATUS_RX_PEND_MASK BIT(11)
> > > +#define I3C_SSTATUS_TXNOTFULL_MASK BIT(12)
> > > +#define I3C_SSTATUS_DACHG_MASK BIT(13)
> > > +#define I3C_SSTATUS_EVDET_MASK GENMASK(21, 20)
> > > +#define I3C_SSTATUS_EVDET_ACKED 0x3
> > > +#define I3C_SSTATUS_IBIDIS_MASK BIT(24)
> > > +#define I3C_SSTATUS_HJDIS_MASK BIT(27)
> > > +
> >
> > There is couple of space formatting here that requires to be fixed.
> >
> > Cheers,
> > Joshua
On Wed, Mar 06, 2024 at 06:10:30PM +0000, Joshua Yeong wrote:
>
> > -----Original Message-----
> > From: Frank Li <[email protected]>
> > Sent: Thursday, March 7, 2024 1:45 AM
> > To: Joshua Yeong <[email protected]>
> > Cc: [email protected]; [email protected];
> > [email protected]; [email protected];
> > [email protected]; [email protected]; [email protected];
> > [email protected]; [email protected];
> > [email protected]; [email protected]; linux-
> > [email protected]; [email protected];
> > [email protected]; [email protected];
> > [email protected]
> > Subject: Re: [PATCH v7 5/8] i3c: target: add svc target controller support
> >
> > On Wed, Mar 06, 2024 at 04:01:17PM +0000, Joshua Yeong wrote:
> > > Hi Frank,
> > >
> > > > -----Original Message-----
> > > > From: linux-i3c <[email protected]> On Behalf Of
> > > > Frank Li
> > > > Sent: Tuesday, February 6, 2024 7:33 AM
> > > > To: [email protected]
> > > > Cc: [email protected]; [email protected];
> > > > [email protected]; [email protected];
> > > > [email protected]; [email protected];
> > > > [email protected]; [email protected];
> > > > [email protected];
> > > > [email protected]; [email protected];
> > > > linux- [email protected]; [email protected];
> > > > [email protected]; [email protected];
> > > > [email protected]
> > > > Subject: [PATCH v7 5/8] i3c: target: add svc target controller
> > > > support
> > > >
> > > > Add Silvaco I3C target controller support
> > > >
> > > > Signed-off-by: Frank Li <[email protected]>
> > > > ---
> > > >
> > > > Notes:
> > > > Change from v2 to v3
> > > > - fix build warning
> > > >
> > > > drivers/i3c/master/Makefile | 2 +-
> > > > drivers/i3c/master/svc-i3c-main.c | 35 +-
> > > > drivers/i3c/master/svc-i3c-target.c | 776
> > >
> > > I think putting target mode files under "master" might not make sense.
> > > We might have to consider that we may have a "secondary master" mode.
> > > Some other ways of splitting or handling of target mode is needed here.
> >
> > I think name 'master' is not good here. Previously only support 'master'
> > mode, it should be fine. Now many controller are dual mode.
> >
> > And I3C spec use 'controller/target' instead of 'master/slave'. I think
> > 'controller' as master are quite confused. It can be master controller and slave
> > controller.
> >
> > Anyway, slave/master may share some code and resource, even only one file.
> >
> > So far, I think it is fine put under master now. we can rename 'master'
> > later when more dual mode controller added.
> >
> > Frank
>
> I am currently working on secondary-controller mode. I would like to explore if
> we can have a different way to implement various modes.
Second controller should be more similar than master mode.
>
> I am guessing that the current inspiration is coming from the existing i2c
> framework.
i2c slave mode is quite simple. The major problem is that the latency to
fill FIFO. Modem async transfer queue should be better handle it. When
host START transfer, fetch one transfer from queue.
There are only a simple I2c slave test driver in kernel. I think not much
people use it.
>
> However, I am thinking of having a more generic function to replace the way how
> we register the i3c driver. (existing 'i3c_master_register' or
> 'devm_i3c_target_ctrl_create' from yours) Maybe along the line where we can have
> all the modes set in a struct and return to the i3c framework. So we only need 1
> API to register all the different modes instead of having one each.
You can try, but it is not easy. Like USB, PCIE-Endpoint, all was use
different register for difference mode.
By the way, rename 'master' as 'controller' should be good idea. A I3C IP
can support (master, slave and second) controller.
I think the folder name should not block issue for this patch series.
Frank
>
> >
> > >
> > > ...
> > >
> > > > +
> > > > +#define I3C_SCONFIG 0x4
> > > > +#define I3C_SCONFIG_SLVENA_MASK BIT(0)
> > > > +#define I3C_SCONFIG_OFFLINE_MASK BIT(9)
> > > > +#define I3C_SCONFIG_SADDR_MASK GENMASK(31, 25)
> > > > +
> > > > +#define I3C_SSTATUS 0x8
> > > > +#define I3C_SSTATUS_STNOTSTOP_MASK BIT(0)
> > > > +#define I3C_SSTATUS_STOP_MASK BIT(10)
> > > > +#define I3C_SSTATUS_RX_PEND_MASK BIT(11)
> > > > +#define I3C_SSTATUS_TXNOTFULL_MASK BIT(12)
> > > > +#define I3C_SSTATUS_DACHG_MASK BIT(13)
> > > > +#define I3C_SSTATUS_EVDET_MASK GENMASK(21, 20)
> > > > +#define I3C_SSTATUS_EVDET_ACKED 0x3
> > > > +#define I3C_SSTATUS_IBIDIS_MASK BIT(24)
> > > > +#define I3C_SSTATUS_HJDIS_MASK BIT(27)
> > > > +
> > >
> > > There is couple of space formatting here that requires to be fixed.
> > >
> > > Cheers,
> > > Joshua