summaryrefslogtreecommitdiff
path: root/drivers/i3c
diff options
context:
space:
mode:
authorIwona Winiarska <iwona.winiarska@intel.com>2022-02-20 01:40:02 +0300
committerIwona Winiarska <iwona.winiarska@intel.com>2022-04-12 14:07:34 +0300
commitbcc82f5f230e4ad36c928f0d2caf93eeb58aa64c (patch)
treefbef76dcdaa17032110b0405fd1c20ab10956b1c /drivers/i3c
parent5da20cec3542029f8bee7ea3855bab90bb96a242 (diff)
downloadlinux-bcc82f5f230e4ad36c928f0d2caf93eeb58aa64c.tar.xz
i3c: Add I3C target support
Extend the existing core implementation with I3C target support. Add missing I3C target driver API. Signed-off-by: Iwona Winiarska <iwona.winiarska@intel.com>
Diffstat (limited to 'drivers/i3c')
-rw-r--r--drivers/i3c/device.c23
-rw-r--r--drivers/i3c/internals.h2
-rw-r--r--drivers/i3c/master.c253
3 files changed, 271 insertions, 7 deletions
diff --git a/drivers/i3c/device.c b/drivers/i3c/device.c
index 671b63a19591..be6669cf0846 100644
--- a/drivers/i3c/device.c
+++ b/drivers/i3c/device.c
@@ -51,6 +51,29 @@ int i3c_device_do_priv_xfers(struct i3c_device *dev,
EXPORT_SYMBOL_GPL(i3c_device_do_priv_xfers);
/**
+ * i3c_device_generate_ibi() - request In-Band Interrupt
+ *
+ * @dev: target device
+ * @data: IBI payload
+ * @len: payload length in bytes
+ *
+ * Request In-Band Interrupt with or without data payload.
+ *
+ * Return: 0 in case of success, a negative error code otherwise.
+ */
+int i3c_device_generate_ibi(struct i3c_device *dev, const u8 *data, int len)
+{
+ int ret;
+
+ i3c_bus_normaluse_lock(dev->bus);
+ ret = i3c_dev_generate_ibi_locked(dev->desc, data, len);
+ i3c_bus_normaluse_unlock(dev->bus);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(i3c_device_generate_ibi);
+
+/**
* i3c_device_get_info() - get I3C device information
*
* @dev: device we want information on
diff --git a/drivers/i3c/internals.h b/drivers/i3c/internals.h
index d633df3bfcf9..524ad47fd916 100644
--- a/drivers/i3c/internals.h
+++ b/drivers/i3c/internals.h
@@ -9,6 +9,7 @@
#define I3C_INTERNALS_H
#include <linux/i3c/master.h>
+#include <linux/i3c/target.h>
extern struct bus_type i3c_bus_type;
extern const struct device_type i3c_masterdev_type;
@@ -26,4 +27,5 @@ int i3c_dev_request_ibi_locked(struct i3c_dev_desc *dev,
void i3c_dev_free_ibi_locked(struct i3c_dev_desc *dev);
int i3c_dev_getstatus_locked(struct i3c_dev_desc *dev, struct i3c_device_info *info);
int i3c_for_each_dev(void *data, int (*fn)(struct device *, void *));
+int i3c_dev_generate_ibi_locked(struct i3c_dev_desc *dev, const u8 *data, int len);
#endif /* I3C_INTERNAL_H */
diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index e6faced697cc..737052ec2d0c 100644
--- a/drivers/i3c/master.c
+++ b/drivers/i3c/master.c
@@ -298,19 +298,24 @@ static const struct device_type i3c_device_type = {
.uevent = i3c_device_uevent,
};
+const struct device_type i3c_target_device_type = {
+};
+
static int i3c_device_match(struct device *dev, struct device_driver *drv)
{
struct i3c_device *i3cdev;
struct i3c_driver *i3cdrv;
- if (dev->type != &i3c_device_type)
+ if (dev->type != &i3c_device_type && dev->type != &i3c_target_device_type)
return 0;
i3cdev = dev_to_i3cdev(dev);
i3cdrv = drv_to_i3cdrv(drv);
- if (i3c_device_match_id(i3cdev, i3cdrv->id_table))
- return 1;
+ if ((dev->type == &i3c_device_type && !i3cdrv->target) ||
+ (dev->type == &i3c_target_device_type && i3cdrv->target))
+ if (i3c_device_match_id(i3cdev, i3cdrv->id_table))
+ return 1;
return 0;
}
@@ -330,7 +335,8 @@ static void i3c_device_remove(struct device *dev)
if (driver->remove)
driver->remove(i3cdev);
- i3c_device_free_ibi(i3cdev);
+ if (!driver->target)
+ i3c_device_free_ibi(i3cdev);
}
struct bus_type i3c_bus_type = {
@@ -2690,6 +2696,211 @@ int i3c_master_unregister(struct i3c_master_controller *master)
}
EXPORT_SYMBOL_GPL(i3c_master_unregister);
+static int i3c_target_bus_init(struct i3c_master_controller *master)
+{
+ return master->target_ops->bus_init(master);
+}
+
+static void i3c_target_bus_cleanup(struct i3c_master_controller *master)
+{
+ if (master->target_ops->bus_cleanup)
+ master->target_ops->bus_cleanup(master);
+}
+
+static void i3c_targetdev_release(struct device *dev)
+{
+ struct i3c_master_controller *master = container_of(dev, struct i3c_master_controller, dev);
+ struct i3c_bus *bus = &master->bus;
+
+ mutex_lock(&i3c_core_lock);
+ idr_remove(&i3c_bus_idr, bus->id);
+ mutex_unlock(&i3c_core_lock);
+
+ of_node_put(dev->of_node);
+}
+
+static void i3c_target_device_release(struct device *dev)
+{
+ struct i3c_device *i3cdev = dev_to_i3cdev(dev);
+ struct i3c_dev_desc *desc = i3cdev->desc;
+
+ kfree(i3cdev);
+ kfree(desc);
+}
+
+static void
+i3c_target_register_new_i3c_dev(struct i3c_master_controller *master, struct i3c_device_info info)
+{
+ struct i3c_dev_desc *desc;
+ int ret;
+
+ desc = kzalloc(sizeof(*desc), GFP_KERNEL);
+ if (!desc)
+ return;
+
+ desc->dev = kzalloc(sizeof(*desc->dev), GFP_KERNEL);
+ if (!desc->dev) {
+ kfree(desc);
+ return;
+ }
+
+ desc->dev->bus = &master->bus;
+ desc->dev->desc = desc;
+ desc->dev->dev.parent = &master->dev;
+ desc->dev->dev.type = &i3c_target_device_type;
+ desc->dev->dev.bus = &i3c_bus_type;
+ desc->dev->dev.release = i3c_target_device_release;
+ desc->info = info;
+ desc->common.master = master;
+ dev_set_name(&desc->dev->dev, "%d-target", master->bus.id);
+
+ ret = device_register(&desc->dev->dev);
+ if (ret)
+ dev_err(&master->dev, "Failed to add I3C target device (err = %d)\n", ret);
+
+ master->this = desc;
+}
+
+static void i3c_target_unregister_i3c_dev(struct i3c_master_controller *master)
+{
+ struct i3c_dev_desc *i3cdev = master->this;
+
+ if (device_is_registered(&i3cdev->dev->dev))
+ device_unregister(&i3cdev->dev->dev);
+ else
+ put_device(&i3cdev->dev->dev);
+}
+
+static void i3c_target_read_device_info(struct device_node *np, struct i3c_device_info *info)
+{
+ u64 pid;
+ u32 dcr;
+ int ret;
+
+ ret = of_property_read_u64(np, "pid", &pid);
+ if (ret)
+ info->pid = 0;
+ else
+ info->pid = pid;
+
+ ret = of_property_read_u32(np, "dcr", &dcr);
+ if (ret)
+ info->pid = 0;
+ else
+ info->dcr = dcr;
+}
+
+static int i3c_target_check_ops(const struct i3c_target_ops *ops)
+{
+ if (!ops || !ops->bus_init)
+ return -EINVAL;
+
+ return 0;
+}
+
+int i3c_target_register(struct i3c_master_controller *master, struct device *parent,
+ const struct i3c_target_ops *ops)
+{
+ struct i3c_bus *i3cbus = i3c_master_get_bus(master);
+ struct i3c_device_info info;
+ int ret;
+
+ ret = i3c_target_check_ops(ops);
+ if (ret)
+ return ret;
+
+ master->dev.parent = parent;
+ master->dev.of_node = of_node_get(parent->of_node);
+ master->dev.bus = &i3c_bus_type;
+ master->dev.release = i3c_targetdev_release;
+ master->target_ops = ops;
+ i3cbus->mode = I3C_BUS_MODE_PURE;
+
+ init_rwsem(&i3cbus->lock);
+ mutex_lock(&i3c_core_lock);
+ ret = idr_alloc(&i3c_bus_idr, i3cbus, 0, 0, GFP_KERNEL);
+ mutex_unlock(&i3c_core_lock);
+ if (ret < 0)
+ return ret;
+ i3cbus->id = ret;
+
+ device_initialize(&master->dev);
+ dev_set_name(&master->dev, "i3c-%d", i3cbus->id);
+
+ ret = device_add(&master->dev);
+ if (ret)
+ goto err_put_device;
+
+ i3c_target_read_device_info(master->dev.of_node, &info);
+
+ i3c_target_register_new_i3c_dev(master, info);
+
+ ret = i3c_target_bus_init(master);
+ if (ret)
+ goto err_cleanup_bus;
+
+ return 0;
+
+err_cleanup_bus:
+ i3c_target_bus_cleanup(master);
+
+err_put_device:
+ put_device(&master->dev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(i3c_target_register);
+
+int i3c_target_unregister(struct i3c_master_controller *master)
+{
+ i3c_target_unregister_i3c_dev(master);
+ i3c_target_bus_cleanup(master);
+ device_unregister(&master->dev);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(i3c_target_unregister);
+
+int i3c_target_read_register(struct i3c_device *dev, const struct i3c_target_read_setup *setup)
+{
+ dev->desc->target_info.read_handler = setup->handler;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(i3c_target_read_register);
+
+int i3c_register(struct i3c_master_controller *master,
+ struct device *parent,
+ const struct i3c_master_controller_ops *master_ops,
+ const struct i3c_target_ops *target_ops,
+ bool secondary)
+{
+ const char *role;
+ int ret;
+
+ ret = of_property_read_string(parent->of_node, "initial-role", &role);
+ if (ret || !strcmp("primary", role)) {
+ return i3c_master_register(master, parent, master_ops, secondary);
+ } else if (!strcmp("target", role)) {
+ master->target = true;
+ return i3c_target_register(master, parent, target_ops);
+ } else {
+ return -EOPNOTSUPP;
+ }
+}
+EXPORT_SYMBOL_GPL(i3c_register);
+
+int i3c_unregister(struct i3c_master_controller *master)
+{
+ if (master->target)
+ i3c_target_unregister(master);
+ else
+ i3c_master_unregister(master);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(i3c_unregister);
+
int i3c_dev_do_priv_xfers_locked(struct i3c_dev_desc *dev,
struct i3c_priv_xfer *xfers,
int nxfers)
@@ -2703,10 +2914,38 @@ int i3c_dev_do_priv_xfers_locked(struct i3c_dev_desc *dev,
if (!master || !xfers)
return -EINVAL;
- if (!master->ops->priv_xfers)
- return -ENOTSUPP;
+ if (!master->target) {
+ if (!master->ops->priv_xfers)
+ return -EOPNOTSUPP;
+
+ return master->ops->priv_xfers(dev, xfers, nxfers);
+ }
+
+ if (!master->target_ops->priv_xfers)
+ return -EOPNOTSUPP;
+
+ return master->target_ops->priv_xfers(dev, xfers, nxfers);
+}
+
+int i3c_dev_generate_ibi_locked(struct i3c_dev_desc *dev, const u8 *data, int len)
+
+{
+ struct i3c_master_controller *master;
+
+ if (!dev)
+ return -ENOENT;
+
+ master = i3c_dev_get_master(dev);
+ if (!master)
+ return -EINVAL;
+
+ if (!master->target)
+ return -EINVAL;
+
+ if (!master->target_ops->generate_ibi)
+ return -EOPNOTSUPP;
- return master->ops->priv_xfers(dev, xfers, nxfers);
+ return master->target_ops->generate_ibi(dev, data, len);
}
int i3c_dev_disable_ibi_locked(struct i3c_dev_desc *dev)