summaryrefslogtreecommitdiff
path: root/drivers/i3c/master.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/i3c/master.c')
-rw-r--r--drivers/i3c/master.c306
1 files changed, 266 insertions, 40 deletions
diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index 425e36b36009..588395e7a770 100644
--- a/drivers/i3c/master.c
+++ b/drivers/i3c/master.c
@@ -106,6 +106,38 @@ static struct i3c_master_controller *dev_to_i3cmaster(struct device *dev)
return container_of(dev, struct i3c_master_controller, dev);
}
+static int __must_check i3c_master_rpm_get(struct i3c_master_controller *master)
+{
+ int ret = master->rpm_allowed ? pm_runtime_resume_and_get(master->dev.parent) : 0;
+
+ if (ret < 0) {
+ dev_err(master->dev.parent, "runtime resume failed, error %d\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+static void i3c_master_rpm_put(struct i3c_master_controller *master)
+{
+ if (master->rpm_allowed)
+ pm_runtime_put_autosuspend(master->dev.parent);
+}
+
+int i3c_bus_rpm_get(struct i3c_bus *bus)
+{
+ return i3c_master_rpm_get(i3c_bus_to_i3c_master(bus));
+}
+
+void i3c_bus_rpm_put(struct i3c_bus *bus)
+{
+ i3c_master_rpm_put(i3c_bus_to_i3c_master(bus));
+}
+
+bool i3c_bus_rpm_ibi_allowed(struct i3c_bus *bus)
+{
+ return i3c_bus_to_i3c_master(bus)->rpm_ibi_allowed;
+}
+
static const struct device_type i3c_device_type;
static struct i3c_bus *dev_to_i3cbus(struct device *dev)
@@ -338,14 +370,6 @@ static void i3c_device_remove(struct device *dev)
i3c_device_free_ibi(i3cdev);
}
-const struct bus_type i3c_bus_type = {
- .name = "i3c",
- .match = i3c_device_match,
- .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_mask(struct i3c_bus *bus, u16 addr, u32 mask)
{
@@ -603,6 +627,14 @@ static ssize_t i2c_scl_frequency_show(struct device *dev,
}
static DEVICE_ATTR_RO(i2c_scl_frequency);
+static void i3c_master_hj_work_fn(struct work_struct *work)
+{
+ struct i3c_master_controller *master = container_of(work, typeof(*master), hj_work);
+
+ if (!master->shutting_down)
+ i3c_master_do_daa(master);
+}
+
static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
{
int ret;
@@ -613,9 +645,17 @@ static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
if (!master->ops->enable_hotjoin || !master->ops->disable_hotjoin)
return -EINVAL;
+ if (enable || master->rpm_ibi_allowed) {
+ ret = i3c_master_rpm_get(master);
+ if (ret)
+ return ret;
+ }
+
i3c_bus_normaluse_lock(&master->bus);
- if (enable)
+ if (master->shutting_down)
+ ret = -ENODEV;
+ else if (enable)
ret = master->ops->enable_hotjoin(master);
else
ret = master->ops->disable_hotjoin(master);
@@ -625,6 +665,9 @@ static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
i3c_bus_normaluse_unlock(&master->bus);
+ if ((enable && ret) || (!enable && !ret) || master->rpm_ibi_allowed)
+ i3c_master_rpm_put(master);
+
return ret;
}
@@ -672,6 +715,18 @@ int i3c_master_disable_hotjoin(struct i3c_master_controller *master)
}
EXPORT_SYMBOL_GPL(i3c_master_disable_hotjoin);
+/**
+ * i3c_master_queue_hotjoin - Queue DAA processing after a Hot-Join event
+ * @master: I3C master object
+ *
+ * Queue the hot-join worker on the master's workqueue.
+ */
+void i3c_master_queue_hotjoin(struct i3c_master_controller *master)
+{
+ queue_work(master->wq, &master->hj_work);
+}
+EXPORT_SYMBOL_GPL(i3c_master_queue_hotjoin);
+
static ssize_t hotjoin_show(struct device *dev, struct device_attribute *da, char *buf)
{
struct i3c_bus *i3cbus = dev_to_i3cbus(dev);
@@ -686,6 +741,49 @@ static ssize_t hotjoin_show(struct device *dev, struct device_attribute *da, cha
static DEVICE_ATTR_RW(hotjoin);
+static ssize_t dev_nack_retry_count_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i3c_bus *i3cbus = dev_to_i3cbus(dev);
+ ssize_t ret;
+
+ i3c_bus_normaluse_lock(i3cbus);
+ ret = sysfs_emit(buf, "%u\n", dev_to_i3cmaster(dev)->dev_nack_retry_count);
+ i3c_bus_normaluse_unlock(i3cbus);
+
+ return ret;
+}
+
+static ssize_t dev_nack_retry_count_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i3c_bus *i3cbus = dev_to_i3cbus(dev);
+ struct i3c_master_controller *master = dev_to_i3cmaster(dev);
+ unsigned long val;
+ int ret;
+
+ ret = kstrtoul(buf, 0, &val);
+ if (ret)
+ return ret;
+
+ ret = i3c_master_rpm_get(master);
+ if (ret)
+ return ret;
+
+ i3c_bus_maintenance_lock(i3cbus);
+ ret = master->ops->set_dev_nack_retry(master, val);
+ if (!ret)
+ master->dev_nack_retry_count = val;
+ i3c_bus_maintenance_unlock(i3cbus);
+
+ i3c_master_rpm_put(master);
+
+ return ret ?: count;
+}
+
+static DEVICE_ATTR_RW(dev_nack_retry_count);
+
static struct attribute *i3c_masterdev_attrs[] = {
&dev_attr_mode.attr,
&dev_attr_current_master.attr,
@@ -719,6 +817,31 @@ static const struct device_type i3c_masterdev_type = {
.groups = i3c_masterdev_groups,
};
+static void i3c_master_shutdown(struct i3c_master_controller *master)
+{
+ i3c_bus_maintenance_lock(&master->bus);
+ master->shutting_down = true;
+ i3c_bus_maintenance_unlock(&master->bus);
+
+ cancel_work_sync(&master->hj_work);
+ cancel_work_sync(&master->reg_work);
+}
+
+static void i3c_device_shutdown(struct device *dev)
+{
+ if (dev->type == &i3c_masterdev_type)
+ i3c_master_shutdown(dev_to_i3cmaster(dev));
+}
+
+const struct bus_type i3c_bus_type = {
+ .name = "i3c",
+ .match = i3c_device_match,
+ .probe = i3c_device_probe,
+ .remove = i3c_device_remove,
+ .shutdown = i3c_device_shutdown,
+};
+EXPORT_SYMBOL_GPL(i3c_bus_type);
+
static int i3c_bus_set_mode(struct i3c_bus *i3cbus, enum i3c_bus_mode mode,
unsigned long max_i2c_scl_rate)
{
@@ -944,6 +1067,10 @@ static int i3c_master_rstdaa_locked(struct i3c_master_controller *master,
ret = i3c_master_send_ccc_cmd_locked(master, &cmd);
i3c_ccc_cmd_dest_cleanup(&dest);
+ /* No active devices on the bus. */
+ if (ret && cmd.err == I3C_ERROR_M2)
+ ret = 0;
+
return ret;
}
@@ -1695,37 +1822,77 @@ i3c_master_register_new_i3c_devs(struct i3c_master_controller *master)
}
}
+static void i3c_master_reg_work_fn(struct work_struct *work)
+{
+ struct i3c_master_controller *master = container_of(work, typeof(*master), reg_work);
+
+ i3c_bus_normaluse_lock(&master->bus);
+ if (!master->shutting_down)
+ i3c_master_register_new_i3c_devs(master);
+ i3c_bus_normaluse_unlock(&master->bus);
+}
+
/**
- * i3c_master_do_daa() - do a DAA (Dynamic Address Assignment)
- * @master: master doing the DAA
- *
- * This function is instantiating an I3C device object and adding it to the
- * I3C device list. All device information are automatically retrieved using
- * standard CCC commands.
+ * i3c_master_do_daa_ext() - Dynamic Address Assignment (extended version)
+ * @master: controller
+ * @rstdaa: whether to first perform Reset of Dynamic Addresses (RSTDAA)
*
- * The I3C device object is returned in case the master wants to attach
- * private data to it using i3c_dev_set_master_data().
+ * Perform Dynamic Address Assignment with optional support for System
+ * Hibernation (@rstdaa is true).
*
- * This function must be called with the bus lock held in write mode.
+ * After System Hibernation, Dynamic Addresses can have been reassigned at boot
+ * time to different values. A simple strategy is followed to handle that.
+ * Perform a Reset of Dynamic Addresses (RSTDAA) followed by the normal DAA
+ * procedure which has provision for reassigning addresses that differ from the
+ * previously recorded addresses.
*
* Return: a 0 in case of success, an negative error code otherwise.
*/
-int i3c_master_do_daa(struct i3c_master_controller *master)
+int i3c_master_do_daa_ext(struct i3c_master_controller *master, bool rstdaa)
{
+ int rstret = 0;
int ret;
+ ret = i3c_master_rpm_get(master);
+ if (ret)
+ return ret;
+
i3c_bus_maintenance_lock(&master->bus);
- ret = master->ops->do_daa(master);
+
+ if (master->shutting_down) {
+ ret = -ENODEV;
+ } else {
+ if (rstdaa)
+ rstret = i3c_master_rstdaa_locked(master, I3C_BROADCAST_ADDR);
+ ret = master->ops->do_daa(master);
+ }
+
i3c_bus_maintenance_unlock(&master->bus);
if (ret)
- return ret;
+ goto out;
- i3c_bus_normaluse_lock(&master->bus);
- i3c_master_register_new_i3c_devs(master);
- i3c_bus_normaluse_unlock(&master->bus);
+ queue_work(master->wq, &master->reg_work);
+out:
+ i3c_master_rpm_put(master);
- return 0;
+ return rstret ?: ret;
+}
+EXPORT_SYMBOL_GPL(i3c_master_do_daa_ext);
+
+/**
+ * i3c_master_do_daa() - do a DAA (Dynamic Address Assignment)
+ * @master: master doing the DAA
+ *
+ * This function instantiates I3C device objects and adds them to the
+ * I3C device list. All device information is automatically retrieved using
+ * standard CCC commands.
+ *
+ * Return: a 0 in case of success, an negative error code otherwise.
+ */
+int i3c_master_do_daa(struct i3c_master_controller *master)
+{
+ return i3c_master_do_daa_ext(master, false);
}
EXPORT_SYMBOL_GPL(i3c_master_do_daa);
@@ -1990,7 +2157,7 @@ static int i3c_master_bus_init(struct i3c_master_controller *master)
* (assigned by the bootloader for example).
*/
ret = i3c_master_rstdaa_locked(master, I3C_BROADCAST_ADDR);
- if (ret && ret != I3C_ERROR_M2)
+ if (ret)
goto err_bus_cleanup;
if (master->ops->set_speed) {
@@ -2068,8 +2235,17 @@ err_detach_devs:
static void i3c_master_bus_cleanup(struct i3c_master_controller *master)
{
- if (master->ops->bus_cleanup)
- master->ops->bus_cleanup(master);
+ if (master->ops->bus_cleanup) {
+ int ret = i3c_master_rpm_get(master);
+
+ if (ret) {
+ dev_err(&master->dev,
+ "runtime resume error: master bus_cleanup() not done\n");
+ } else {
+ master->ops->bus_cleanup(master);
+ i3c_master_rpm_put(master);
+ }
+ }
i3c_master_detach_free_devs(master);
}
@@ -2129,12 +2305,11 @@ int i3c_master_add_i3c_dev_locked(struct i3c_master_controller *master,
bool enable_ibi = false;
int ret;
- if (!master)
- return -EINVAL;
-
newdev = i3c_master_alloc_i3c_dev(master, &info);
- if (IS_ERR(newdev))
- return PTR_ERR(newdev);
+ if (IS_ERR(newdev)) {
+ ret = PTR_ERR(newdev);
+ goto err_prevent_addr_reuse;
+ }
ret = i3c_master_attach_i3c_dev(master, newdev);
if (ret)
@@ -2256,6 +2431,16 @@ err_detach_dev:
err_free_dev:
i3c_master_free_i3c_dev(newdev);
+err_prevent_addr_reuse:
+ /*
+ * Although the device has not been added, the address has been
+ * assigned. Prevent the address from being used again.
+ */
+ if (i3c_bus_get_addr_slot_status(&master->bus, addr) == I3C_ADDR_SLOT_FREE)
+ i3c_bus_set_addr_slot_status(&master->bus, addr, I3C_ADDR_SLOT_I3C_DEV);
+
+ dev_err(&master->dev, "Failed to add I3C device at address %u, error %d\n", addr, ret);
+
return ret;
}
EXPORT_SYMBOL_GPL(i3c_master_add_i3c_dev_locked);
@@ -2424,6 +2609,10 @@ static int i3c_master_i2c_adapter_xfer(struct i2c_adapter *adap,
return -EOPNOTSUPP;
}
+ ret = i3c_master_rpm_get(master);
+ if (ret)
+ return ret;
+
i3c_bus_normaluse_lock(&master->bus);
dev = i3c_master_find_i2c_dev_by_addr(master, addr);
if (!dev)
@@ -2432,6 +2621,8 @@ static int i3c_master_i2c_adapter_xfer(struct i2c_adapter *adap,
ret = master->ops->i2c_xfers(dev, xfers, nxfers);
i3c_bus_normaluse_unlock(&master->bus);
+ i3c_master_rpm_put(master);
+
return ret ? ret : nxfers;
}
@@ -2534,6 +2725,10 @@ static int i3c_i2c_notifier_call(struct notifier_block *nb, unsigned long action
master = i2c_adapter_to_i3c_master(adap);
+ ret = i3c_master_rpm_get(master);
+ if (ret)
+ return ret;
+
i3c_bus_maintenance_lock(&master->bus);
switch (action) {
case BUS_NOTIFY_ADD_DEVICE:
@@ -2547,6 +2742,8 @@ static int i3c_i2c_notifier_call(struct notifier_block *nb, unsigned long action
}
i3c_bus_maintenance_unlock(&master->bus);
+ i3c_master_rpm_put(master);
+
return ret;
}
@@ -2884,6 +3081,10 @@ int i3c_master_register(struct i3c_master_controller *master,
INIT_LIST_HEAD(&master->boardinfo.i2c);
INIT_LIST_HEAD(&master->boardinfo.i3c);
+ ret = i3c_master_rpm_get(master);
+ if (ret)
+ return ret;
+
device_initialize(&master->dev);
master->dev.dma_mask = parent->dma_mask;
@@ -2927,11 +3128,13 @@ int i3c_master_register(struct i3c_master_controller *master,
if (ret)
goto err_put_dev;
- master->wq = alloc_workqueue("%s", 0, 0, dev_name(parent));
+ master->wq = alloc_workqueue("%s", WQ_PERCPU | WQ_FREEZABLE, 0, dev_name(parent));
if (!master->wq) {
ret = -ENOMEM;
goto err_put_dev;
}
+ INIT_WORK(&master->hj_work, i3c_master_hj_work_fn);
+ INIT_WORK(&master->reg_work, i3c_master_reg_work_fn);
ret = i3c_master_bus_init(master);
if (ret)
@@ -2957,12 +3160,20 @@ int i3c_master_register(struct i3c_master_controller *master,
/*
* We're done initializing the bus and the controller, we can now
- * register I3C devices discovered during the initial DAA.
+ * register I3C devices discovered during the initial DAA. Device
+ * registration is done via reg_work because that keeps a single
+ * registration code path and ensures the worker is the only writer
+ * of desc->dev. Flush the work to preserve synchronous probe-time
+ * behavior.
*/
master->init_done = true;
- i3c_bus_normaluse_lock(&master->bus);
- i3c_master_register_new_i3c_devs(master);
- i3c_bus_normaluse_unlock(&master->bus);
+ queue_work(master->wq, &master->reg_work);
+ flush_work(&master->reg_work);
+
+ if (master->ops->set_dev_nack_retry)
+ device_create_file(&master->dev, &dev_attr_dev_nack_retry_count);
+
+ i3c_master_rpm_put(master);
return 0;
@@ -2973,6 +3184,7 @@ err_cleanup_bus:
i3c_master_bus_cleanup(master);
err_put_dev:
+ i3c_master_rpm_put(master);
put_device(&master->dev);
return ret;
@@ -2988,6 +3200,10 @@ EXPORT_SYMBOL_GPL(i3c_master_register);
void i3c_master_unregister(struct i3c_master_controller *master)
{
i3c_bus_notify(&master->bus, I3C_NOTIFY_BUS_REMOVE);
+ i3c_master_shutdown(master);
+
+ if (master->ops->set_dev_nack_retry)
+ device_remove_file(&master->dev, &dev_attr_dev_nack_retry_count);
i3c_master_i2c_adapter_cleanup(master);
i3c_master_unregister_i3c_devs(master);
@@ -3118,8 +3334,18 @@ void i3c_dev_free_ibi_locked(struct i3c_dev_desc *dev)
if (!dev->ibi)
return;
- if (WARN_ON(dev->ibi->enabled))
- WARN_ON(i3c_dev_disable_ibi_locked(dev));
+ if (dev->ibi->enabled) {
+ int ret;
+
+ dev_err(&master->dev, "Freeing IBI that is still enabled\n");
+ ret = i3c_master_rpm_get(master);
+ if (!ret) {
+ ret = i3c_dev_disable_ibi_locked(dev);
+ i3c_master_rpm_put(master);
+ }
+ if (ret)
+ dev_err(&master->dev, "Failed to disable IBI before freeing\n");
+ }
master->ops->free_ibi(dev);