summaryrefslogtreecommitdiff
path: root/drivers/i3c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/i3c')
-rw-r--r--drivers/i3c/Kconfig15
-rw-r--r--drivers/i3c/Makefile1
-rw-r--r--drivers/i3c/i3cdev.c428
-rw-r--r--drivers/i3c/internals.h2
-rw-r--r--drivers/i3c/master.c115
-rw-r--r--drivers/i3c/master/Kconfig9
-rw-r--r--drivers/i3c/master/Makefile1
-rw-r--r--drivers/i3c/master/aspeed-i3c-global.c106
-rw-r--r--drivers/i3c/master/dw-i3c-master.c289
9 files changed, 927 insertions, 39 deletions
diff --git a/drivers/i3c/Kconfig b/drivers/i3c/Kconfig
index 30a441506f61..01642768ab5f 100644
--- a/drivers/i3c/Kconfig
+++ b/drivers/i3c/Kconfig
@@ -20,5 +20,20 @@ menuconfig I3C
will be called i3c.
if I3C
+
+config I3CDEV
+ tristate "I3C device interface"
+ depends on I3C
+ help
+ Say Y here to use i3c-* device files, usually found in the /dev
+ directory on your system. They make it possible to have user-space
+ programs use the I3C devices.
+
+ This support is also available as a module. If so, the module
+ will be called i3cdev.
+
+ Note that this application programming interface is EXPERIMENTAL
+ and hence SUBJECT TO CHANGE WITHOUT NOTICE while it stabilizes.
+
source "drivers/i3c/master/Kconfig"
endif # I3C
diff --git a/drivers/i3c/Makefile b/drivers/i3c/Makefile
index 11982efbc6d9..606d422841b2 100644
--- a/drivers/i3c/Makefile
+++ b/drivers/i3c/Makefile
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
i3c-y := device.o master.o
obj-$(CONFIG_I3C) += i3c.o
+obj-$(CONFIG_I3CDEV) += i3cdev.o
obj-$(CONFIG_I3C) += master/
diff --git a/drivers/i3c/i3cdev.c b/drivers/i3c/i3cdev.c
new file mode 100644
index 000000000000..949715a9dfdb
--- /dev/null
+++ b/drivers/i3c/i3cdev.c
@@ -0,0 +1,428 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2019 Synopsys, Inc. and/or its affiliates.
+ *
+ * Author: Vitor Soares <soares@synopsys.com>
+ */
+
+#include <linux/cdev.h>
+#include <linux/compat.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include <linux/i3c/i3cdev.h>
+
+#include "internals.h"
+
+struct i3cdev_data {
+ struct list_head list;
+ struct i3c_device *i3c;
+ struct cdev cdev;
+ struct device *dev;
+ int id;
+};
+
+static DEFINE_IDA(i3cdev_ida);
+static dev_t i3cdev_number;
+#define I3C_MINORS 32 /* 32 I3C devices supported for now */
+
+static LIST_HEAD(i3cdev_list);
+static DEFINE_SPINLOCK(i3cdev_list_lock);
+
+static struct i3cdev_data *i3cdev_get_by_i3c(struct i3c_device *i3c)
+{
+ struct i3cdev_data *i3cdev;
+
+ spin_lock(&i3cdev_list_lock);
+ list_for_each_entry(i3cdev, &i3cdev_list, list) {
+ if (i3cdev->i3c == i3c)
+ goto found;
+ }
+
+ i3cdev = NULL;
+
+found:
+ spin_unlock(&i3cdev_list_lock);
+ return i3cdev;
+}
+
+static struct i3cdev_data *get_free_i3cdev(struct i3c_device *i3c)
+{
+ struct i3cdev_data *i3cdev;
+ int id;
+
+ id = ida_simple_get(&i3cdev_ida, 0, I3C_MINORS, GFP_KERNEL);
+ if (id < 0) {
+ pr_err("i3cdev: no minor number available!\n");
+ return ERR_PTR(id);
+ }
+
+ i3cdev = kzalloc(sizeof(*i3cdev), GFP_KERNEL);
+ if (!i3cdev) {
+ ida_simple_remove(&i3cdev_ida, id);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ i3cdev->i3c = i3c;
+ i3cdev->id = id;
+
+ spin_lock(&i3cdev_list_lock);
+ list_add_tail(&i3cdev->list, &i3cdev_list);
+ spin_unlock(&i3cdev_list_lock);
+
+ return i3cdev;
+}
+
+static void put_i3cdev(struct i3cdev_data *i3cdev)
+{
+ spin_lock(&i3cdev_list_lock);
+ list_del(&i3cdev->list);
+ spin_unlock(&i3cdev_list_lock);
+ kfree(i3cdev);
+}
+
+static ssize_t
+i3cdev_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos)
+{
+ struct i3c_device *i3c = file->private_data;
+ struct i3c_priv_xfer xfers = {
+ .rnw = true,
+ .len = count,
+ };
+ char *tmp;
+ int ret;
+
+ tmp = kzalloc(count, GFP_KERNEL);
+ if (!tmp)
+ return -ENOMEM;
+
+ xfers.data.in = tmp;
+
+ dev_dbg(&i3c->dev, "Reading %zu bytes.\n", count);
+
+ ret = i3c_device_do_priv_xfers(i3c, &xfers, 1);
+ if (!ret)
+ ret = copy_to_user(buf, tmp, count) ? -EFAULT : ret;
+
+ kfree(tmp);
+ return ret;
+}
+
+static ssize_t
+i3cdev_write(struct file *file, const char __user *buf, size_t count,
+ loff_t *f_pos)
+{
+ struct i3c_device *i3c = file->private_data;
+ struct i3c_priv_xfer xfers = {
+ .rnw = false,
+ .len = count,
+ };
+ char *tmp;
+ int ret;
+
+ tmp = memdup_user(buf, count);
+ if (IS_ERR(tmp))
+ return PTR_ERR(tmp);
+
+ xfers.data.out = tmp;
+
+ dev_dbg(&i3c->dev, "Writing %zu bytes.\n", count);
+
+ ret = i3c_device_do_priv_xfers(i3c, &xfers, 1);
+ kfree(tmp);
+ return (!ret) ? count : ret;
+}
+
+static int
+i3cdev_do_priv_xfer(struct i3c_device *dev, struct i3c_ioc_priv_xfer *xfers,
+ unsigned int nxfers)
+{
+ struct i3c_priv_xfer *k_xfers;
+ u8 **data_ptrs;
+ int i, ret = 0;
+
+ k_xfers = kcalloc(nxfers, sizeof(*k_xfers), GFP_KERNEL);
+ if (!k_xfers)
+ return -ENOMEM;
+
+ data_ptrs = kcalloc(nxfers, sizeof(*data_ptrs), GFP_KERNEL);
+ if (!data_ptrs) {
+ ret = -ENOMEM;
+ goto err_free_k_xfer;
+ }
+
+ for (i = 0; i < nxfers; i++) {
+ data_ptrs[i] = memdup_user((const u8 __user *)
+ (uintptr_t)xfers[i].data,
+ xfers[i].len);
+ if (IS_ERR(data_ptrs[i])) {
+ ret = PTR_ERR(data_ptrs[i]);
+ break;
+ }
+
+ k_xfers[i].len = xfers[i].len;
+ if (xfers[i].rnw) {
+ k_xfers[i].rnw = true;
+ k_xfers[i].data.in = data_ptrs[i];
+ } else {
+ k_xfers[i].rnw = false;
+ k_xfers[i].data.out = data_ptrs[i];
+ }
+ }
+
+ if (ret < 0) {
+ i--;
+ goto err_free_mem;
+ }
+
+ ret = i3c_device_do_priv_xfers(dev, k_xfers, nxfers);
+ if (ret)
+ goto err_free_mem;
+
+ for (i = 0; i < nxfers; i++) {
+ if (xfers[i].rnw) {
+ if (copy_to_user((void __user *)(uintptr_t)xfers[i].data,
+ data_ptrs[i], xfers[i].len))
+ ret = -EFAULT;
+ }
+ }
+
+err_free_mem:
+ for (; i >= 0; i--)
+ kfree(data_ptrs[i]);
+ kfree(data_ptrs);
+err_free_k_xfer:
+ kfree(k_xfers);
+ return ret;
+}
+
+static struct i3c_ioc_priv_xfer *
+i3cdev_get_ioc_priv_xfer(unsigned int cmd, struct i3c_ioc_priv_xfer *u_xfers,
+ unsigned int *nxfers)
+{
+ u32 tmp = _IOC_SIZE(cmd);
+
+ if ((tmp % sizeof(struct i3c_ioc_priv_xfer)) != 0)
+ return ERR_PTR(-EINVAL);
+
+ *nxfers = tmp / sizeof(struct i3c_ioc_priv_xfer);
+ if (*nxfers == 0)
+ return NULL;
+
+ return memdup_user(u_xfers, tmp);
+}
+
+static int
+i3cdev_ioc_priv_xfer(struct i3c_device *i3c, unsigned int cmd,
+ struct i3c_ioc_priv_xfer *u_xfers)
+{
+ struct i3c_ioc_priv_xfer *k_xfers;
+ unsigned int nxfers;
+ int ret;
+
+ k_xfers = i3cdev_get_ioc_priv_xfer(cmd, u_xfers, &nxfers);
+ if (IS_ERR_OR_NULL(k_xfers))
+ return PTR_ERR(k_xfers);
+
+ ret = i3cdev_do_priv_xfer(i3c, k_xfers, nxfers);
+
+ kfree(k_xfers);
+
+ return ret;
+}
+
+static long
+i3cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct i3c_device *i3c = file->private_data;
+
+ dev_dbg(&i3c->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n", cmd, arg);
+
+ if (_IOC_TYPE(cmd) != I3C_DEV_IOC_MAGIC)
+ return -ENOTTY;
+
+ /* Check command number and direction */
+ if (_IOC_NR(cmd) == _IOC_NR(I3C_IOC_PRIV_XFER(0)) &&
+ _IOC_DIR(cmd) == (_IOC_READ | _IOC_WRITE))
+ return i3cdev_ioc_priv_xfer(i3c, cmd,
+ (struct i3c_ioc_priv_xfer __user *)arg);
+
+ return 0;
+}
+
+static int i3cdev_open(struct inode *inode, struct file *file)
+{
+ struct i3cdev_data *i3cdev = container_of(inode->i_cdev,
+ struct i3cdev_data,
+ cdev);
+
+ file->private_data = i3cdev->i3c;
+
+ return 0;
+}
+
+static int i3cdev_release(struct inode *inode, struct file *file)
+{
+ file->private_data = NULL;
+
+ return 0;
+}
+
+static const struct file_operations i3cdev_fops = {
+ .owner = THIS_MODULE,
+ .read = i3cdev_read,
+ .write = i3cdev_write,
+ .unlocked_ioctl = i3cdev_ioctl,
+ .open = i3cdev_open,
+ .release = i3cdev_release,
+};
+
+/* ------------------------------------------------------------------------- */
+
+static struct class *i3cdev_class;
+
+static int i3cdev_attach(struct device *dev, void *dummy)
+{
+ struct i3cdev_data *i3cdev;
+ struct i3c_device *i3c;
+ int res;
+
+ if (dev->type == &i3c_masterdev_type || dev->driver)
+ return 0;
+
+ i3c = dev_to_i3cdev(dev);
+
+ /* Get a device */
+ i3cdev = get_free_i3cdev(i3c);
+ if (IS_ERR(i3cdev))
+ return PTR_ERR(i3cdev);
+
+ cdev_init(&i3cdev->cdev, &i3cdev_fops);
+ i3cdev->cdev.owner = THIS_MODULE;
+ res = cdev_add(&i3cdev->cdev,
+ MKDEV(MAJOR(i3cdev_number), i3cdev->id), 1);
+ if (res)
+ goto error_cdev;
+
+ /* register this i3c device with the driver core */
+ i3cdev->dev = device_create(i3cdev_class, &i3c->dev,
+ MKDEV(MAJOR(i3cdev_number), i3cdev->id),
+ NULL, "i3c-%s", dev_name(&i3c->dev));
+ if (IS_ERR(i3cdev->dev)) {
+ res = PTR_ERR(i3cdev->dev);
+ goto error;
+ }
+ pr_debug("i3cdev: I3C device [%s] registered as minor %d\n",
+ dev_name(&i3c->dev), i3cdev->id);
+ return 0;
+
+error:
+ cdev_del(&i3cdev->cdev);
+error_cdev:
+ put_i3cdev(i3cdev);
+ return res;
+}
+
+static int i3cdev_detach(struct device *dev, void *dummy)
+{
+ struct i3cdev_data *i3cdev;
+ struct i3c_device *i3c;
+
+ if (dev->type == &i3c_masterdev_type)
+ return 0;
+
+ i3c = dev_to_i3cdev(dev);
+
+ i3cdev = i3cdev_get_by_i3c(i3c);
+ if (!i3cdev)
+ return 0;
+
+ cdev_del(&i3cdev->cdev);
+ device_destroy(i3cdev_class, MKDEV(MAJOR(i3cdev_number), i3cdev->id));
+ ida_simple_remove(&i3cdev_ida, i3cdev->id);
+ put_i3cdev(i3cdev);
+
+ pr_debug("i3cdev: device [%s] unregistered\n", dev_name(&i3c->dev));
+
+ return 0;
+}
+
+static int i3cdev_notifier_call(struct notifier_block *nb,
+ unsigned long action,
+ void *data)
+{
+ struct device *dev = data;
+
+ switch (action) {
+ case BUS_NOTIFY_ADD_DEVICE:
+ case BUS_NOTIFY_UNBOUND_DRIVER:
+ return i3cdev_attach(dev, NULL);
+ case BUS_NOTIFY_DEL_DEVICE:
+ case BUS_NOTIFY_BOUND_DRIVER:
+ return i3cdev_detach(dev, NULL);
+ }
+
+ return 0;
+}
+
+static struct notifier_block i3c_notifier = {
+ .notifier_call = i3cdev_notifier_call,
+};
+
+static int __init i3cdev_init(void)
+{
+ int res;
+
+ /* Dynamically request unused major number */
+ res = alloc_chrdev_region(&i3cdev_number, 0, I3C_MINORS, "i3c");
+ if (res)
+ goto out;
+
+ /* Create a classe to populate sysfs entries*/
+ i3cdev_class = class_create(THIS_MODULE, "i3cdev");
+ if (IS_ERR(i3cdev_class)) {
+ res = PTR_ERR(i3cdev_class);
+ goto out_unreg_chrdev;
+ }
+
+ /* Keep track of busses which have devices to add or remove later */
+ res = bus_register_notifier(&i3c_bus_type, &i3c_notifier);
+ if (res)
+ goto out_unreg_class;
+
+ /* Bind to already existing device without driver right away */
+ i3c_for_each_dev(NULL, i3cdev_attach);
+
+ return 0;
+
+out_unreg_class:
+ class_destroy(i3cdev_class);
+out_unreg_chrdev:
+ unregister_chrdev_region(i3cdev_number, I3C_MINORS);
+out:
+ pr_err("%s: Driver Initialisation failed\n", __FILE__);
+ return res;
+}
+
+static void __exit i3cdev_exit(void)
+{
+ bus_unregister_notifier(&i3c_bus_type, &i3c_notifier);
+ i3c_for_each_dev(NULL, i3cdev_detach);
+ class_destroy(i3cdev_class);
+ unregister_chrdev_region(i3cdev_number, I3C_MINORS);
+}
+
+MODULE_AUTHOR("Vitor Soares <soares@synopsys.com>");
+MODULE_DESCRIPTION("I3C /dev entries driver");
+MODULE_LICENSE("GPL");
+
+module_init(i3cdev_init);
+module_exit(i3cdev_exit);
diff --git a/drivers/i3c/internals.h b/drivers/i3c/internals.h
index 86b7b44cfca2..a6deedf5ce06 100644
--- a/drivers/i3c/internals.h
+++ b/drivers/i3c/internals.h
@@ -11,6 +11,7 @@
#include <linux/i3c/master.h>
extern struct bus_type i3c_bus_type;
+extern const struct device_type i3c_masterdev_type;
void i3c_bus_normaluse_lock(struct i3c_bus *bus);
void i3c_bus_normaluse_unlock(struct i3c_bus *bus);
@@ -23,4 +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_for_each_dev(void *data, int (*fn)(struct device *, void *));
#endif /* I3C_INTERNAL_H */
diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index 1c6b78ad5ade..05415fbc117d 100644
--- a/drivers/i3c/master.c
+++ b/drivers/i3c/master.c
@@ -343,6 +343,7 @@ struct bus_type i3c_bus_type = {
.probe = i3c_device_probe,
.remove = i3c_device_remove,
};
+EXPORT_SYMBOL_GPL(i3c_bus_type);
static enum i3c_addr_slot_status
i3c_bus_get_addr_slot_status(struct i3c_bus *bus, u16 addr)
@@ -545,9 +546,10 @@ static void i3c_masterdev_release(struct device *dev)
of_node_put(dev->of_node);
}
-static const struct device_type i3c_masterdev_type = {
+const struct device_type i3c_masterdev_type = {
.groups = i3c_masterdev_groups,
};
+EXPORT_SYMBOL_GPL(i3c_masterdev_type);
static int i3c_bus_set_mode(struct i3c_bus *i3cbus, enum i3c_bus_mode mode,
unsigned long max_i2c_scl_rate)
@@ -992,6 +994,21 @@ static int i3c_master_setda_locked(struct i3c_master_controller *master,
return ret;
}
+static int i3c_master_setaasa_locked(struct i3c_master_controller *master)
+{
+ struct i3c_ccc_cmd_dest dest;
+ struct i3c_ccc_cmd cmd;
+ int ret;
+
+ i3c_ccc_cmd_dest_init(&dest, I3C_BROADCAST_ADDR, 0);
+ i3c_ccc_cmd_init(&cmd, false, I3C_CCC_SETAASA, &dest, 1);
+
+ ret = i3c_master_send_ccc_cmd_locked(master, &cmd);
+ i3c_ccc_cmd_dest_cleanup(&dest);
+
+ return ret;
+}
+
static int i3c_master_setdasa_locked(struct i3c_master_controller *master,
u8 static_addr, u8 dyn_addr)
{
@@ -1004,6 +1021,26 @@ static int i3c_master_setnewda_locked(struct i3c_master_controller *master,
return i3c_master_setda_locked(master, oldaddr, newaddr, false);
}
+static int i3c_master_sethid_locked(struct i3c_master_controller *master)
+{
+ struct i3c_ccc_cmd_dest dest;
+ struct i3c_ccc_cmd cmd;
+ struct i3c_ccc_sethid *sethid;
+ int ret;
+
+ sethid = i3c_ccc_cmd_dest_init(&dest, I3C_BROADCAST_ADDR, 1);
+ if (!sethid)
+ return -ENOMEM;
+
+ sethid->hid = 0;
+ i3c_ccc_cmd_init(&cmd, false, I3C_CCC_SETHID, &dest, 1);
+
+ ret = i3c_master_send_ccc_cmd_locked(master, &cmd);
+ i3c_ccc_cmd_dest_cleanup(&dest);
+
+ return ret;
+}
+
static int i3c_master_getmrl_locked(struct i3c_master_controller *master,
struct i3c_device_info *info)
{
@@ -1238,6 +1275,11 @@ static int i3c_master_retrieve_dev_info(struct i3c_dev_desc *dev)
slot_status == I3C_ADDR_SLOT_I2C_DEV)
return -EINVAL;
+ if (master->jdec_spd) {
+ dev->info.pid = dev->boardinfo->pid;
+ return 0;
+ }
+
ret = i3c_master_getpid_locked(master, &dev->info);
if (ret)
return ret;
@@ -1453,13 +1495,20 @@ static int i3c_master_early_i3c_dev_add(struct i3c_master_controller *master,
if (ret)
goto err_free_dev;
- ret = i3c_master_setdasa_locked(master, i3cdev->info.static_addr,
+ if (master->jdec_spd) {
+ i3cdev->info.dyn_addr = i3cdev->boardinfo->init_dyn_addr;
+ ret = i3c_master_reattach_i3c_dev(i3cdev,
+ i3cdev->info.static_addr);
+ } else {
+ ret = i3c_master_setdasa_locked(master,
+ i3cdev->info.static_addr,
i3cdev->boardinfo->init_dyn_addr);
- if (ret)
- goto err_detach_dev;
+ if (ret)
+ goto err_detach_dev;
- i3cdev->info.dyn_addr = i3cdev->boardinfo->init_dyn_addr;
- ret = i3c_master_reattach_i3c_dev(i3cdev, 0);
+ i3cdev->info.dyn_addr = i3cdev->boardinfo->init_dyn_addr;
+ ret = i3c_master_reattach_i3c_dev(i3cdev, 0);
+ }
if (ret)
goto err_rstdaa;
@@ -1534,9 +1583,14 @@ int i3c_master_do_daa(struct i3c_master_controller *master)
{
int ret;
- i3c_bus_maintenance_lock(&master->bus);
- ret = master->ops->do_daa(master);
- i3c_bus_maintenance_unlock(&master->bus);
+ if (master->jdec_spd) {
+ ret = i3c_master_sethid_locked(master);
+ ret = i3c_master_setaasa_locked(master);
+ } else {
+ i3c_bus_maintenance_lock(&master->bus);
+ ret = master->ops->do_daa(master);
+ i3c_bus_maintenance_unlock(&master->bus);
+ }
if (ret)
return ret;
@@ -1673,8 +1727,9 @@ static int i3c_master_bus_init(struct i3c_master_controller *master)
enum i3c_addr_slot_status status;
struct i2c_dev_boardinfo *i2cboardinfo;
struct i3c_dev_boardinfo *i3cboardinfo;
+ struct i3c_dev_desc *i3cdev, *i3ctmp;
struct i2c_dev_desc *i2cdev;
- int ret;
+ int ret, n_i3cdev = 0;
/*
* First attach all devices with static definitions provided by the
@@ -1762,9 +1817,10 @@ static int i3c_master_bus_init(struct i3c_master_controller *master)
goto err_rstdaa;
}
- i3c_bus_set_addr_slot_status(&master->bus,
- i3cboardinfo->init_dyn_addr,
- I3C_ADDR_SLOT_I3C_DEV);
+ if (i3cboardinfo->static_addr != i3cboardinfo->init_dyn_addr)
+ i3c_bus_set_addr_slot_status(&master->bus,
+ i3cboardinfo->init_dyn_addr,
+ I3C_ADDR_SLOT_I3C_DEV);
/*
* Only try to create/attach devices that have a static
@@ -1776,8 +1832,17 @@ static int i3c_master_bus_init(struct i3c_master_controller *master)
if (i3cboardinfo->static_addr)
i3c_master_early_i3c_dev_add(master, i3cboardinfo);
+
+ n_i3cdev++;
}
+ /*
+ * Since SPD devices are all with static address. Don't do DAA if we
+ * know it is a pure I2C bus.
+ */
+ if (master->jdec_spd && n_i3cdev == 0)
+ return 0;
+
ret = i3c_master_do_daa(master);
if (ret)
goto err_rstdaa;
@@ -2104,12 +2169,22 @@ static int of_populate_i3c_bus(struct i3c_master_controller *master)
struct device *dev = &master->dev;
struct device_node *i3cbus_np = dev->of_node;
struct device_node *node;
- int ret;
+ int ret, i;
u32 val;
if (!i3cbus_np)
return 0;
+ if (of_get_property(i3cbus_np, "jdec-spd", NULL))
+ master->jdec_spd = 1;
+
+ /* For SPD bus, undo unnecessary address reservations. */
+ if (master->jdec_spd) {
+ for (i = 0; i < 7; i++)
+ i3c_bus_set_addr_slot_status(&master->bus, I3C_BROADCAST_ADDR ^ BIT(i),
+ I3C_ADDR_SLOT_FREE);
+ }
+
for_each_available_child_of_node(i3cbus_np, node) {
ret = of_i3c_master_add_dev(master, node);
if (ret) {
@@ -2698,6 +2773,18 @@ void i3c_dev_free_ibi_locked(struct i3c_dev_desc *dev)
dev->ibi = NULL;
}
+int i3c_for_each_dev(void *data, int (*fn)(struct device *, void *))
+{
+ int res;
+
+ mutex_lock(&i3c_core_lock);
+ res = bus_for_each_dev(&i3c_bus_type, NULL, data, fn);
+ mutex_unlock(&i3c_core_lock);
+
+ return res;
+}
+EXPORT_SYMBOL_GPL(i3c_for_each_dev);
+
static int __init i3c_init(void)
{
return bus_register(&i3c_bus_type);
diff --git a/drivers/i3c/master/Kconfig b/drivers/i3c/master/Kconfig
index 4e80a1fcbf91..0eecba763754 100644
--- a/drivers/i3c/master/Kconfig
+++ b/drivers/i3c/master/Kconfig
@@ -21,3 +21,12 @@ config DW_I3C_MASTER
This driver can also be built as a module. If so, the module
will be called dw-i3c-master.
+
+config ASPEED_I3C_MASTER
+ tristate "Aspeed I3C master driver"
+ depends on I3C
+ depends on MACH_ASPEED_G6
+ select DW_I3C_MASTER
+ help
+ Aspeed I3C master controller is a Synopsys DesignWare I3C controller
+ plus additional global control.
diff --git a/drivers/i3c/master/Makefile b/drivers/i3c/master/Makefile
index 7eea9e086144..1fb766509ff2 100644
--- a/drivers/i3c/master/Makefile
+++ b/drivers/i3c/master/Makefile
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_ASPEED_I3C_MASTER) += aspeed-i3c-global.o
obj-$(CONFIG_CDNS_I3C_MASTER) += i3c-master-cdns.o
obj-$(CONFIG_DW_I3C_MASTER) += dw-i3c-master.o
diff --git a/drivers/i3c/master/aspeed-i3c-global.c b/drivers/i3c/master/aspeed-i3c-global.c
new file mode 100644
index 000000000000..93292ad6652d
--- /dev/null
+++ b/drivers/i3c/master/aspeed-i3c-global.c
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2019 ASPEED Technology Inc.
+
+#include <linux/clk.h>
+#include <linux/irq.h>
+#include <linux/irqchip.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/io.h>
+#include <linux/reset.h>
+#include <linux/delay.h>
+
+#define ASPEED_I3CG_CTRL(x) (0x10 + (x * 0x10))
+#define ASPEED_I3CG_SET(x) (0x14 + (x * 0x10))
+
+#define DEF_SLV_INST_ID 0x4
+#define DEF_SLV_STATIC_ADDR 0x74
+
+union i3c_set_reg {
+ uint32_t value;
+ struct {
+ unsigned int i2c_mode : 1; /* bit[0] */
+ unsigned int test_mode : 1; /* bit[1] */
+ unsigned int act_mode : 2; /* bit[ 3: 2] */
+ unsigned int pending_int : 4; /* bit[ 7: 4] */
+ unsigned int sa : 7; /* bit[14: 8] */
+ unsigned int sa_en : 1; /* bit[15] */
+ unsigned int inst_id : 4; /* bit[19:16] */
+ unsigned int rsvd : 12; /* bit[31:20] */
+ } fields;
+};
+
+
+struct aspeed_i3c_global {
+ void __iomem *base;
+ struct reset_control *rst;
+};
+
+static const struct of_device_id aspeed_i3c_global_of_table[] = {
+ { .compatible = "aspeed,ast2600-i3c-global", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, aspeed_i3c_global_of_table);
+
+static int aspeed_i3c_global_probe(struct platform_device *pdev)
+{
+ struct aspeed_i3c_global *i3c_global;
+ union i3c_set_reg reg;
+ int i, ret;
+ u32 num_of_i3cs;
+
+ i3c_global = devm_kzalloc(&pdev->dev, sizeof(*i3c_global), GFP_KERNEL);
+ if (!i3c_global)
+ return -ENOMEM;
+
+ i3c_global->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(i3c_global->base))
+ return PTR_ERR(i3c_global->base);
+
+ i3c_global->rst = devm_reset_control_get(&pdev->dev, NULL);
+ if (IS_ERR(i3c_global->rst)) {
+ dev_err(&pdev->dev,
+ "missing or invalid reset controller device tree entry");
+ return PTR_ERR(i3c_global->rst);
+ }
+
+ reset_control_assert(i3c_global->rst);
+ udelay(3);
+ reset_control_deassert(i3c_global->rst);
+
+ ret = of_property_read_u32(pdev->dev.of_node, "ni3cs", &num_of_i3cs);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "unable to get number of i3c devices");
+ return -EINVAL;
+ }
+
+ reg.value = 0;
+ reg.fields.inst_id = DEF_SLV_INST_ID;
+ reg.fields.sa = DEF_SLV_STATIC_ADDR;
+ reg.fields.pending_int = 0xc;
+ reg.fields.act_mode = 0x1;
+ for (i = 0; i < num_of_i3cs; i++)
+ writel(reg.value, i3c_global->base + ASPEED_I3CG_SET(i));
+
+ dev_info(&pdev->dev, "i3c global control registered\n");
+
+ return 0;
+}
+
+static struct platform_driver aspeed_i3c_driver = {
+ .probe = aspeed_i3c_global_probe,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = of_match_ptr(aspeed_i3c_global_of_table),
+ },
+};
+module_platform_driver(aspeed_i3c_driver);
+
+MODULE_AUTHOR("Ryan Chen");
+MODULE_DESCRIPTION("ASPEED I3C Global Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c
index 8513bd353c05..00f49e7435fe 100644
--- a/drivers/i3c/master/dw-i3c-master.c
+++ b/drivers/i3c/master/dw-i3c-master.c
@@ -21,11 +21,14 @@
#include <linux/reset.h>
#include <linux/slab.h>
+//#define IBI_WIP
+#define CCC_WORKAROUND
#define DEVICE_CTRL 0x0
#define DEV_CTRL_ENABLE BIT(31)
#define DEV_CTRL_RESUME BIT(30)
#define DEV_CTRL_HOT_JOIN_NACK BIT(8)
#define DEV_CTRL_I2C_SLAVE_PRESENT BIT(7)
+#define DEV_CTRL_IBI_DATA_EN BIT(1)
#define DEVICE_ADDR 0x4
#define DEV_ADDR_DYNAMIC_ADDR_VALID BIT(31)
@@ -74,7 +77,14 @@
#define RX_TX_DATA_PORT 0x14
#define IBI_QUEUE_STATUS 0x18
+#define IBI_QUEUE_DATA 0x18
+#define IBI_QUEUE_DATA_STATUS_MASK GENMASK(31, 28)
+#define IBI_QUEUE_DATA_PAYLOAD_MASK GENMASK(15, 8)
#define QUEUE_THLD_CTRL 0x1c
+#define QUEUE_THLD_CTRL_IBI_STA_MASK GENMASK(31, 24)
+#define QUEUE_THLD_CTRL_IBI_STA(x) (((x) - 1) << 24)
+#define QUEUE_THLD_CTRL_IBI_DAT_MASK GENMASK(23, 16)
+#define QUEUE_THLD_CTRL_IBI_DAT(x) ((x) << 16)
#define QUEUE_THLD_CTRL_RESP_BUF_MASK GENMASK(15, 8)
#define QUEUE_THLD_CTRL_RESP_BUF(x) (((x) - 1) << 8)
@@ -125,9 +135,14 @@
INTR_IBI_THLD_STAT | \
INTR_TX_THLD_STAT | \
INTR_RX_THLD_STAT)
-
+#ifdef IBI_WIP
+#define INTR_MASTER_MASK (INTR_TRANSFER_ERR_STAT | \
+ INTR_RESP_READY_STAT | \
+ INTR_IBI_THLD_STAT)
+#else
#define INTR_MASTER_MASK (INTR_TRANSFER_ERR_STAT | \
INTR_RESP_READY_STAT)
+#endif
#define QUEUE_STATUS_LEVEL 0x4c
#define QUEUE_STATUS_IBI_STATUS_CNT(x) (((x) & GENMASK(28, 24)) >> 24)
@@ -185,7 +200,14 @@
#define SLAVE_CONFIG 0xec
#define DEV_ADDR_TABLE_LEGACY_I2C_DEV BIT(31)
+#ifdef IBI_WIP
+#define DEV_ADDR_TABLE_IBI_WITH_DATA BIT(12)
+#define DEV_ADDR_TABLE_IBI_PEC_EN BIT(11)
+#define DEV_ADDR_TABLE_DYNAMIC_ADDR(x) \
+ ((((x) << 16) & GENMASK(23, 16)) | DEV_ADDR_TABLE_IBI_WITH_DATA)
+#else
#define DEV_ADDR_TABLE_DYNAMIC_ADDR(x) (((x) << 16) & GENMASK(23, 16))
+#endif
#define DEV_ADDR_TABLE_STATIC_ADDR(x) ((x) & GENMASK(6, 0))
#define DEV_ADDR_TABLE_LOC(start, idx) ((start) + ((idx) << 2))
@@ -198,6 +220,10 @@
#define I3C_BUS_I2C_FM_TLOW_MIN_NS 1300
#define I3C_BUS_I2C_FMP_TLOW_MIN_NS 500
#define I3C_BUS_THIGH_MAX_NS 41
+#define I3C_BUS_OP_TLOW_MIN_NS 500
+#define I3C_BUS_OP_THIGH_MIN_NS 260
+#define I3C_BUS_PP_TLOW_MIN_NS 35
+#define I3C_BUS_PP_THIGH_MIN_NS 35
#define XFER_TIMEOUT (msecs_to_jiffies(1000))
@@ -225,6 +251,7 @@ struct dw_i3c_xfer {
};
struct dw_i3c_master {
+ struct device *dev;
struct i3c_master_controller base;
u16 maxdevs;
u16 datstartaddr;
@@ -286,6 +313,8 @@ static bool dw_i3c_master_supports_ccc_cmd(struct i3c_master_controller *m,
case I3C_CCC_GETSTATUS:
case I3C_CCC_GETMXDS:
case I3C_CCC_GETHDRCAP:
+ case I3C_CCC_SETAASA:
+ case I3C_CCC_SETHID:
return true;
default:
return false;
@@ -306,7 +335,7 @@ static void dw_i3c_master_disable(struct dw_i3c_master *master)
static void dw_i3c_master_enable(struct dw_i3c_master *master)
{
- writel(readl(master->regs + DEVICE_CTRL) | DEV_CTRL_ENABLE,
+ writel(readl(master->regs + DEVICE_CTRL) | DEV_CTRL_ENABLE | DEV_CTRL_IBI_DATA_EN,
master->regs + DEVICE_CTRL);
}
@@ -339,6 +368,7 @@ static void dw_i3c_master_wr_tx_fifo(struct dw_i3c_master *master,
memcpy(&tmp, bytes + (nbytes & ~3), nbytes & 3);
writesl(master->regs + RX_TX_DATA_PORT, &tmp, 1);
+ dev_dbg(master->dev, "TX data = %08x\n", tmp);
}
}
@@ -454,6 +484,27 @@ static void dw_i3c_master_end_xfer_locked(struct dw_i3c_master *master, u32 isr)
int i, ret = 0;
u32 nresp;
+#ifdef IBI_WIP
+ int j = 0;
+ u32 nibi;
+
+ /* consume the IBI data */
+ nibi = readl(master->regs + QUEUE_STATUS_LEVEL);
+ nibi = QUEUE_STATUS_IBI_BUF_BLR(nibi);
+
+ if ((isr & INTR_IBI_THLD_STAT) && nibi) {
+ u32 ibi;
+
+ for (i = 0; i < nibi; i++) {
+ ibi = readl(master->regs + IBI_QUEUE_DATA);
+ for (j = 0; j < (ibi & 0xff); j += 4)
+ dev_dbg(master->dev, "ibi: %08x\n",
+ readl(master->regs + IBI_QUEUE_DATA));
+ }
+ writel(RESET_CTRL_IBI_QUEUE, master->regs + RESET_CTRL);
+ }
+#endif
+
if (!xfer)
return;
@@ -517,7 +568,7 @@ static void dw_i3c_master_end_xfer_locked(struct dw_i3c_master *master, u32 isr)
static int dw_i3c_clk_cfg(struct dw_i3c_master *master)
{
- unsigned long core_rate, core_period;
+ unsigned long core_rate, core_period, scl_period_h, scl_period_l;
u32 scl_timing;
u8 hcnt, lcnt;
@@ -527,23 +578,71 @@ static int dw_i3c_clk_cfg(struct dw_i3c_master *master)
core_period = DIV_ROUND_UP(1000000000, core_rate);
- hcnt = DIV_ROUND_UP(I3C_BUS_THIGH_MAX_NS, core_period) - 1;
- if (hcnt < SCL_I3C_TIMING_CNT_MIN)
- hcnt = SCL_I3C_TIMING_CNT_MIN;
+ if (master->base.jdec_spd) {
+ /* set open-drain timing according to I2C SCL frequency */
+ if (master->base.bus.scl_rate.i2c) {
+ scl_period_h = scl_period_l =
+ DIV_ROUND_UP(1000000000,
+ master->base.bus.scl_rate.i2c) >> 1;
+ } else {
+ /* default: I2C SCL = 400kHz (fast mode) */
+ scl_period_h = scl_period_l =
+ DIV_ROUND_UP(1000000000, 400000) >> 1;
+ }
- lcnt = DIV_ROUND_UP(core_rate, I3C_BUS_TYP_I3C_SCL_RATE) - hcnt;
- if (lcnt < SCL_I3C_TIMING_CNT_MIN)
- lcnt = SCL_I3C_TIMING_CNT_MIN;
+ if (scl_period_h < I3C_BUS_OP_THIGH_MIN_NS)
+ scl_period_h = I3C_BUS_OP_THIGH_MIN_NS;
+ if (scl_period_l < I3C_BUS_OP_TLOW_MIN_NS)
+ scl_period_l = I3C_BUS_OP_TLOW_MIN_NS;
+ hcnt = DIV_ROUND_UP(scl_period_h, core_period) + 1;
+ lcnt = DIV_ROUND_UP(scl_period_l, core_period) + 1;
+ scl_timing = SCL_I3C_TIMING_HCNT(hcnt) | SCL_I3C_TIMING_LCNT(lcnt);
+ writel(scl_timing, master->regs + SCL_I3C_OD_TIMING);
+ scl_timing = SCL_I2C_FM_TIMING_HCNT(hcnt) | SCL_I2C_FM_TIMING_LCNT(lcnt);
+ writel(scl_timing, master->regs + SCL_I2C_FM_TIMING);
+ scl_timing = SCL_I2C_FMP_TIMING_HCNT(hcnt) | SCL_I2C_FMP_TIMING_LCNT(lcnt);
+ writel(scl_timing, master->regs + SCL_I2C_FMP_TIMING);
+
+ if (!(readl(master->regs + DEVICE_CTRL) & DEV_CTRL_I2C_SLAVE_PRESENT))
+ writel(BUS_I3C_MST_FREE(lcnt), master->regs + BUS_FREE_TIMING);
+
+ /* set push-pull timing according to I3C SCL frequency */
+ if (master->base.bus.scl_rate.i3c) {
+ scl_period_h = scl_period_l =
+ DIV_ROUND_UP(1000000000,
+ master->base.bus.scl_rate.i3c) >> 1;
+ } else {
+ /* default: I3C SCL = 12.5MHz */
+ scl_period_h = scl_period_l =
+ DIV_ROUND_UP(1000000000, 12500000) >> 1;
+ }
+ if (scl_period_h < I3C_BUS_PP_THIGH_MIN_NS)
+ scl_period_h = I3C_BUS_PP_THIGH_MIN_NS;
+ if (scl_period_l < I3C_BUS_PP_TLOW_MIN_NS)
+ scl_period_l = I3C_BUS_PP_TLOW_MIN_NS;
+ hcnt = DIV_ROUND_UP(scl_period_h, core_period) + 1;
+ lcnt = DIV_ROUND_UP(scl_period_l, core_period) + 1;
+ scl_timing = SCL_I3C_TIMING_HCNT(hcnt) | SCL_I3C_TIMING_LCNT(lcnt);
+ writel(scl_timing, master->regs + SCL_I3C_PP_TIMING);
+ } else {
+ hcnt = DIV_ROUND_UP(I3C_BUS_THIGH_MAX_NS, core_period) - 1;
+ if (hcnt < SCL_I3C_TIMING_CNT_MIN)
+ hcnt = SCL_I3C_TIMING_CNT_MIN;
+
+ lcnt = DIV_ROUND_UP(core_rate, I3C_BUS_TYP_I3C_SCL_RATE) - hcnt;
+ if (lcnt < SCL_I3C_TIMING_CNT_MIN)
+ lcnt = SCL_I3C_TIMING_CNT_MIN;
- scl_timing = SCL_I3C_TIMING_HCNT(hcnt) | SCL_I3C_TIMING_LCNT(lcnt);
- writel(scl_timing, master->regs + SCL_I3C_PP_TIMING);
+ scl_timing = SCL_I3C_TIMING_HCNT(hcnt) | SCL_I3C_TIMING_LCNT(lcnt);
+ writel(scl_timing, master->regs + SCL_I3C_PP_TIMING);
- if (!(readl(master->regs + DEVICE_CTRL) & DEV_CTRL_I2C_SLAVE_PRESENT))
- writel(BUS_I3C_MST_FREE(lcnt), master->regs + BUS_FREE_TIMING);
+ if (!(readl(master->regs + DEVICE_CTRL) & DEV_CTRL_I2C_SLAVE_PRESENT))
+ writel(BUS_I3C_MST_FREE(lcnt), master->regs + BUS_FREE_TIMING);
- lcnt = DIV_ROUND_UP(I3C_BUS_TLOW_OD_MIN_NS, core_period);
- scl_timing = SCL_I3C_TIMING_HCNT(hcnt) | SCL_I3C_TIMING_LCNT(lcnt);
- writel(scl_timing, master->regs + SCL_I3C_OD_TIMING);
+ lcnt = DIV_ROUND_UP(I3C_BUS_TLOW_OD_MIN_NS, core_period);
+ scl_timing = SCL_I3C_TIMING_HCNT(hcnt) | SCL_I3C_TIMING_LCNT(lcnt);
+ writel(scl_timing, master->regs + SCL_I3C_OD_TIMING);
+ }
lcnt = DIV_ROUND_UP(core_rate, I3C_BUS_SDR1_SCL_RATE) - hcnt;
scl_timing = SCL_EXT_LCNT_1(lcnt);
@@ -639,8 +738,20 @@ static int dw_i3c_master_bus_init(struct i3c_master_controller *m)
if (ret)
return ret;
+#ifdef IBI_WIP
+ thld_ctrl = readl(master->regs + QUEUE_THLD_CTRL);
+ thld_ctrl &=
+ ~(QUEUE_THLD_CTRL_IBI_STA_MASK | QUEUE_THLD_CTRL_IBI_DAT_MASK);
+ thld_ctrl |= QUEUE_THLD_CTRL_IBI_STA(1);
+ thld_ctrl |= QUEUE_THLD_CTRL_IBI_DAT(1);
+ writel(thld_ctrl, master->regs + QUEUE_THLD_CTRL);
+
+ writel(0, master->regs + IBI_SIR_REQ_REJECT);
+ writel(0, master->regs + IBI_MR_REQ_REJECT);
+#else
writel(IBI_REQ_REJECT_ALL, master->regs + IBI_SIR_REQ_REJECT);
writel(IBI_REQ_REJECT_ALL, master->regs + IBI_MR_REQ_REJECT);
+#endif
/* For now don't support Hot-Join */
writel(readl(master->regs + DEVICE_CTRL) | DEV_CTRL_HOT_JOIN_NACK,
@@ -688,6 +799,9 @@ static int dw_i3c_ccc_set(struct dw_i3c_master *master,
COMMAND_PORT_TOC |
COMMAND_PORT_ROC;
+ dev_dbg(master->dev, "%s:cmd_hi=0x%08x cmd_lo=0x%08x tx_len=%d id=%x\n",
+ __func__, cmd->cmd_hi, cmd->cmd_lo, cmd->tx_len, ccc->id);
+
dw_i3c_master_enqueue_xfer(master, xfer);
if (!wait_for_completion_timeout(&xfer->comp, XFER_TIMEOUT))
dw_i3c_master_dequeue_xfer(master, xfer);
@@ -729,6 +843,9 @@ static int dw_i3c_ccc_get(struct dw_i3c_master *master, struct i3c_ccc_cmd *ccc)
COMMAND_PORT_TOC |
COMMAND_PORT_ROC;
+ dev_dbg(master->dev, "%s:cmd_hi=0x%08x cmd_lo=0x%08x rx_len=%d id=%x\n",
+ __func__, cmd->cmd_hi, cmd->cmd_lo, cmd->rx_len, ccc->id);
+
dw_i3c_master_enqueue_xfer(master, xfer);
if (!wait_for_completion_timeout(&xfer->comp, XFER_TIMEOUT))
dw_i3c_master_dequeue_xfer(master, xfer);
@@ -746,15 +863,26 @@ static int dw_i3c_master_send_ccc_cmd(struct i3c_master_controller *m,
{
struct dw_i3c_master *master = to_dw_i3c_master(m);
int ret = 0;
+ u32 i3c_pp_timing, i3c_od_timing;
if (ccc->id == I3C_CCC_ENTDAA)
return -EINVAL;
+ i3c_od_timing = readl(master->regs + SCL_I3C_OD_TIMING);
+ i3c_pp_timing = readl(master->regs + SCL_I3C_PP_TIMING);
+ if ((ccc->id == I3C_CCC_SETAASA) || (ccc->id == I3C_CCC_SETHID) ||
+ (ccc->id == I3C_CCC_DEVCTRL)) {
+ writel(i3c_od_timing, master->regs + SCL_I3C_PP_TIMING);
+ }
+
if (ccc->rnw)
ret = dw_i3c_ccc_get(master, ccc);
else
ret = dw_i3c_ccc_set(master, ccc);
+ if ((ccc->id == I3C_CCC_SETAASA) || (ccc->id == I3C_CCC_SETHID))
+ writel(i3c_pp_timing, master->regs + SCL_I3C_PP_TIMING);
+
return ret;
}
@@ -815,15 +943,97 @@ static int dw_i3c_master_daa(struct i3c_master_controller *m)
}
dw_i3c_master_free_xfer(xfer);
-
+#ifdef IBI_WIP
+ ret = i3c_master_enec_locked(m, I3C_BROADCAST_ADDR,
+ I3C_CCC_EVENT_SIR);
+#else
i3c_master_disec_locked(m, I3C_BROADCAST_ADDR,
I3C_CCC_EVENT_HJ |
I3C_CCC_EVENT_MR |
I3C_CCC_EVENT_SIR);
+#endif
return 0;
}
+#ifdef CCC_WORKAROUND
+/*
+ * Provide an interface for sending CCC from userspace. Especially for the
+ * transfers with PEC and direct CCC.
+ */
+static int dw_i3c_master_ccc_xfers(struct i3c_dev_desc *dev,
+ struct i3c_priv_xfer *i3c_xfers,
+ int i3c_nxfers)
+{
+ struct dw_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
+ struct i3c_master_controller *m = i3c_dev_get_master(dev);
+ struct dw_i3c_master *master = to_dw_i3c_master(m);
+ struct dw_i3c_xfer *xfer;
+ int i, ret = 0;
+ struct dw_i3c_cmd *cmd_ccc;
+
+ xfer = dw_i3c_master_alloc_xfer(master, i3c_nxfers);
+ if (!xfer)
+ return -ENOMEM;
+
+ /* i3c_xfers[0] handles the CCC data */
+ cmd_ccc = &xfer->cmds[0];
+ cmd_ccc->cmd_hi = COMMAND_PORT_ARG_DATA_LEN(i3c_xfers[0].len - 1) |
+ COMMAND_PORT_TRANSFER_ARG;
+ cmd_ccc->tx_buf = i3c_xfers[0].data.out + 1;
+ cmd_ccc->tx_len = i3c_xfers[0].len - 1;
+ cmd_ccc->cmd_lo = COMMAND_PORT_SPEED(dev->info.max_write_ds);
+ cmd_ccc->cmd_lo |= COMMAND_PORT_TID(0) |
+ COMMAND_PORT_DEV_INDEX(master->maxdevs - 1) |
+ COMMAND_PORT_ROC;
+ if (i3c_nxfers == 1)
+ cmd_ccc->cmd_lo |= COMMAND_PORT_TOC;
+
+ dev_dbg(master->dev,
+ "%s:cmd_ccc_hi=0x%08x cmd_ccc_lo=0x%08x tx_len=%d\n", __func__,
+ cmd_ccc->cmd_hi, cmd_ccc->cmd_lo, cmd_ccc->tx_len);
+
+ for (i = 1; i < i3c_nxfers; i++) {
+ struct dw_i3c_cmd *cmd = &xfer->cmds[i];
+
+ cmd->cmd_hi = COMMAND_PORT_ARG_DATA_LEN(i3c_xfers[i].len) |
+ COMMAND_PORT_TRANSFER_ARG;
+
+ if (i3c_xfers[i].rnw) {
+ cmd->rx_buf = i3c_xfers[i].data.in;
+ cmd->rx_len = i3c_xfers[i].len;
+ cmd->cmd_lo = COMMAND_PORT_READ_TRANSFER |
+ COMMAND_PORT_SPEED(dev->info.max_read_ds);
+ } else {
+ cmd->tx_buf = i3c_xfers[i].data.out;
+ cmd->tx_len = i3c_xfers[i].len;
+ cmd->cmd_lo =
+ COMMAND_PORT_SPEED(dev->info.max_write_ds);
+ }
+
+ cmd->cmd_lo |= COMMAND_PORT_TID(i) |
+ COMMAND_PORT_DEV_INDEX(data->index) |
+ COMMAND_PORT_ROC;
+
+ if (i == (i3c_nxfers - 1))
+ cmd->cmd_lo |= COMMAND_PORT_TOC;
+
+ dev_dbg(master->dev,
+ "%s:cmd_hi=0x%08x cmd_lo=0x%08x tx_len=%d rx_len=%d\n",
+ __func__, cmd->cmd_hi, cmd->cmd_lo, cmd->tx_len,
+ cmd->rx_len);
+ }
+
+ dw_i3c_master_enqueue_xfer(master, xfer);
+ if (!wait_for_completion_timeout(&xfer->comp, XFER_TIMEOUT))
+ dw_i3c_master_dequeue_xfer(master, xfer);
+
+ ret = xfer->ret;
+ dw_i3c_master_free_xfer(xfer);
+
+ return ret;
+}
+#endif
static int dw_i3c_master_priv_xfers(struct i3c_dev_desc *dev,
struct i3c_priv_xfer *i3c_xfers,
int i3c_nxfers)
@@ -852,6 +1062,17 @@ static int dw_i3c_master_priv_xfers(struct i3c_dev_desc *dev,
nrxwords > master->caps.datafifodepth)
return -ENOTSUPP;
+#ifdef CCC_WORKAROUND
+ if (i3c_xfers[0].rnw == 0) {
+ /* write command: check if hit special address */
+ u8 tmp;
+
+ memcpy(&tmp, i3c_xfers[0].data.out, 1);
+ if (tmp == 0xff)
+ return dw_i3c_master_ccc_xfers(dev, i3c_xfers, i3c_nxfers);
+ }
+#endif
+
xfer = dw_i3c_master_alloc_xfer(master, i3c_nxfers);
if (!xfer)
return -ENOMEM;
@@ -881,6 +1102,11 @@ static int dw_i3c_master_priv_xfers(struct i3c_dev_desc *dev,
if (i == (i3c_nxfers - 1))
cmd->cmd_lo |= COMMAND_PORT_TOC;
+
+ dev_dbg(master->dev,
+ "%s:cmd_hi=0x%08x cmd_lo=0x%08x tx_len=%d rx_len=%d\n",
+ __func__, cmd->cmd_hi, cmd->cmd_lo, cmd->tx_len,
+ cmd->rx_len);
}
dw_i3c_master_enqueue_xfer(master, xfer);
@@ -1144,14 +1370,6 @@ static int dw_i3c_probe(struct platform_device *pdev)
spin_lock_init(&master->xferqueue.lock);
INIT_LIST_HEAD(&master->xferqueue.list);
- writel(INTR_ALL, master->regs + INTR_STATUS);
- irq = platform_get_irq(pdev, 0);
- ret = devm_request_irq(&pdev->dev, irq,
- dw_i3c_master_irq_handler, 0,
- dev_name(&pdev->dev), master);
- if (ret)
- goto err_assert_rst;
-
platform_set_drvdata(pdev, master);
/* Information regarding the FIFOs/QUEUEs depth */
@@ -1165,12 +1383,33 @@ static int dw_i3c_probe(struct platform_device *pdev)
master->datstartaddr = ret;
master->maxdevs = ret >> 16;
master->free_pos = GENMASK(master->maxdevs - 1, 0);
+#ifdef CCC_WORKAROUND
+ if (master->maxdevs > 0) {
+ master->free_pos &= ~BIT(master->maxdevs - 1);
+ ret = (even_parity(I3C_BROADCAST_ADDR) << 7) | I3C_BROADCAST_ADDR;
+ master->addrs[master->maxdevs - 1] = ret;
+ writel(DEV_ADDR_TABLE_DYNAMIC_ADDR(ret),
+ master->regs + DEV_ADDR_TABLE_LOC(master->datstartaddr,
+ master->maxdevs - 1));
+ }
+#endif
+
+ writel(INTR_ALL, master->regs + INTR_STATUS);
+ irq = platform_get_irq(pdev, 0);
+ ret = devm_request_irq(&pdev->dev, irq,
+ dw_i3c_master_irq_handler, 0,
+ dev_name(&pdev->dev), master);
+ if (ret)
+ goto err_assert_rst;
ret = i3c_master_register(&master->base, &pdev->dev,
&dw_mipi_i3c_ops, false);
if (ret)
goto err_assert_rst;
+ dev_info(&pdev->dev, "i3c bus %d registered, irq %d\n",
+ master->base.bus.id, irq);
+
return 0;
err_assert_rst: