summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/i3c/device.c23
-rw-r--r--drivers/i3c/internals.h2
-rw-r--r--drivers/i3c/master.c253
-rw-r--r--include/linux/i3c/device.h9
-rw-r--r--include/linux/i3c/master.h23
-rw-r--r--include/linux/i3c/target.h23
6 files changed, 326 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)
diff --git a/include/linux/i3c/device.h b/include/linux/i3c/device.h
index c31624535fbf..e632941575bd 100644
--- a/include/linux/i3c/device.h
+++ b/include/linux/i3c/device.h
@@ -179,6 +179,7 @@ struct i3c_driver {
int (*probe)(struct i3c_device *dev);
void (*remove)(struct i3c_device *dev);
const struct i3c_device_id *id_table;
+ bool target;
};
static inline struct i3c_driver *drv_to_i3cdrv(struct device_driver *drv)
@@ -294,6 +295,8 @@ int i3c_device_do_priv_xfers(struct i3c_device *dev,
struct i3c_priv_xfer *xfers,
int nxfers);
+int i3c_device_generate_ibi(struct i3c_device *dev, const u8 *data, int len);
+
void i3c_device_get_info(struct i3c_device *dev, struct i3c_device_info *info);
struct i3c_ibi_payload {
@@ -335,4 +338,10 @@ int i3c_device_disable_ibi(struct i3c_device *dev);
int i3c_device_getstatus_ccc(struct i3c_device *dev, struct i3c_device_info *info);
+struct i3c_target_read_setup {
+ void (*handler)(struct i3c_device *dev, const u8 *data, size_t len);
+};
+
+int i3c_target_read_register(struct i3c_device *dev, const struct i3c_target_read_setup *setup);
+
#endif /* I3C_DEV_H */
diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h
index 11cdcc062b3e..5d35a6d1c992 100644
--- a/include/linux/i3c/master.h
+++ b/include/linux/i3c/master.h
@@ -22,6 +22,7 @@
#define I3C_BROADCAST_ADDR 0x7e
#define I3C_MAX_ADDR GENMASK(6, 0)
+struct i3c_target_ops;
struct i3c_master_controller;
struct i3c_bus;
struct i2c_device;
@@ -185,10 +186,20 @@ struct i3c_dev_boardinfo {
};
/**
+ * struct i3c_target_info - target information attached to a specific device
+ * @read handler: handler specified at i3c_target_read_register() call time.
+ */
+
+struct i3c_target_info {
+ void (*read_handler)(struct i3c_device *dev, const u8 *data, size_t len);
+};
+
+/**
* struct i3c_dev_desc - I3C device descriptor
* @common: common part of the I3C device descriptor
* @info: I3C device information. Will be automatically filled when you create
* your device with i3c_master_add_i3c_dev_locked()
+ * @target_info: I3C target information.
* @ibi_lock: lock used to protect the &struct_i3c_device->ibi
* @ibi: IBI info attached to a device. Should be NULL until
* i3c_device_request_ibi() is called
@@ -207,6 +218,7 @@ struct i3c_dev_boardinfo {
struct i3c_dev_desc {
struct i3c_i2c_dev_desc common;
struct i3c_device_info info;
+ struct i3c_target_info target_info;
struct mutex ibi_lock;
struct i3c_device_ibi_info *ibi;
struct i3c_device *dev;
@@ -463,6 +475,8 @@ struct i3c_master_controller_ops {
* registered to the I2C subsystem to be as transparent as possible to
* existing I2C drivers
* @ops: master operations. See &struct i3c_master_controller_ops
+ * @target_ops: target operations. See &struct i3c_target_ops
+ * @target: true if the underlying I3C device acts as a target on I3C bus
* @secondary: true if the master is a secondary master
* @init_done: true when the bus initialization is done
* @boardinfo.i3c: list of I3C boardinfo objects
@@ -485,6 +499,8 @@ struct i3c_master_controller {
struct i3c_dev_desc *this;
struct i2c_adapter i2c;
const struct i3c_master_controller_ops *ops;
+ const struct i3c_target_ops *target_ops;
+ unsigned int target : 1;
unsigned int secondary : 1;
unsigned int init_done : 1;
unsigned int jdec_spd : 1;
@@ -545,6 +561,13 @@ int i3c_master_register(struct i3c_master_controller *master,
bool secondary);
int i3c_master_unregister(struct i3c_master_controller *master);
+int i3c_register(struct i3c_master_controller *master,
+ struct device *parent,
+ const struct i3c_master_controller_ops *master_ops,
+ const struct i3c_target_ops *target_ops,
+ bool secondary);
+int i3c_unregister(struct i3c_master_controller *master);
+
/**
* i3c_dev_get_master_data() - get master private data attached to an I3C
* device descriptor
diff --git a/include/linux/i3c/target.h b/include/linux/i3c/target.h
new file mode 100644
index 000000000000..9e71124b5325
--- /dev/null
+++ b/include/linux/i3c/target.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2022, Intel Corporation */
+
+#ifndef I3C_TARGET_H
+#define I3C_TARGET_H
+
+#include <linux/device.h>
+#include <linux/i3c/device.h>
+
+struct i3c_master_controller;
+
+struct i3c_target_ops {
+ int (*bus_init)(struct i3c_master_controller *master);
+ void (*bus_cleanup)(struct i3c_master_controller *master);
+ int (*priv_xfers)(struct i3c_dev_desc *dev, struct i3c_priv_xfer *xfers, int nxfers);
+ int (*generate_ibi)(struct i3c_dev_desc *dev, const u8 *data, int len);
+};
+
+int i3c_target_register(struct i3c_master_controller *master, struct device *parent,
+ const struct i3c_target_ops *ops);
+int i3c_target_unregister(struct i3c_master_controller *master);
+
+#endif