diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2024-05-25 13:28:29 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2024-05-25 13:28:29 -0700 |
commit | 4286e1fceb8c99f25332dc7e85f9879408caa45a (patch) | |
tree | 5f742ce2f595babc749d00ac26173f9d1abf9f64 | |
parent | 6951abe8f37b1f4f9a0e7c036873f0ab4f56abf1 (diff) | |
parent | 1d08326020fba690cbb7b8f1b38ab4eab6745969 (diff) |
Merge tag 'i3c/for-6.10' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux
Pull i3c updates from Alexandre Belloni:
"Runtime PM (power management) is improved and hot-join support has
been added to the dw controller driver.
Core:
- Allow device driver to trigger controller runtime PM
Drivers:
- dw: hot-join support
- svc: better IBI handling"
* tag 'i3c/for-6.10' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux:
i3c: dw: Add hot-join support.
i3c: master: Enable runtime PM for master controller
i3c: master: svc: fix invalidate IBI type and miss call client IBI handler
i3c: master: svc: change ENXIO to EAGAIN when IBI occurs during start frame
i3c: Add comment for -EAGAIN in i3c_device_do_priv_xfers()
-rw-r--r-- | drivers/i3c/device.c | 4 | ||||
-rw-r--r-- | drivers/i3c/master.c | 6 | ||||
-rw-r--r-- | drivers/i3c/master/dw-i3c-master.c | 67 | ||||
-rw-r--r-- | drivers/i3c/master/dw-i3c-master.h | 2 | ||||
-rw-r--r-- | drivers/i3c/master/svc-i3c-master.c | 18 |
5 files changed, 80 insertions, 17 deletions
diff --git a/drivers/i3c/device.c b/drivers/i3c/device.c index 1a6a8703dbc3..e80e48756914 100644 --- a/drivers/i3c/device.c +++ b/drivers/i3c/device.c @@ -27,6 +27,10 @@ * This function can sleep and thus cannot be called in atomic context. * * Return: 0 in case of success, a negative error core otherwise. + * -EAGAIN: controller lost address arbitration. Target + * (IBI, HJ or controller role request) win the bus. Client + * driver needs to resend the 'xfers' some time later. + * See I3C spec ver 1.1.1 09-Jun-2021. Section: 5.1.2.2.3. */ int i3c_device_do_priv_xfers(struct i3c_device *dev, struct i3c_priv_xfer *xfers, diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index f32c591ae325..3b4d6a8edca3 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -13,6 +13,7 @@ #include <linux/kernel.h> #include <linux/list.h> #include <linux/of.h> +#include <linux/pm_runtime.h> #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/workqueue.h> @@ -2812,6 +2813,10 @@ int i3c_master_register(struct i3c_master_controller *master, i3c_bus_notify(i3cbus, I3C_NOTIFY_BUS_ADD); + pm_runtime_no_callbacks(&master->dev); + pm_suspend_ignore_children(&master->dev, true); + pm_runtime_enable(&master->dev); + /* * We're done initializing the bus and the controller, we can now * register I3C devices discovered during the initial DAA. @@ -2849,6 +2854,7 @@ void i3c_master_unregister(struct i3c_master_controller *master) i3c_master_i2c_adapter_cleanup(master); i3c_master_unregister_i3c_devs(master); i3c_master_bus_cleanup(master); + pm_runtime_disable(&master->dev); device_unregister(&master->dev); } EXPORT_SYMBOL_GPL(i3c_master_unregister); diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index 276153e10f5a..0ec00e644bd4 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -1136,6 +1136,23 @@ static void dw_i3c_master_free_ibi(struct i3c_dev_desc *dev) data->ibi_pool = NULL; } +static void dw_i3c_master_enable_sir_signal(struct dw_i3c_master *master, bool enable) +{ + u32 reg; + + reg = readl(master->regs + INTR_STATUS_EN); + reg &= ~INTR_IBI_THLD_STAT; + if (enable) + reg |= INTR_IBI_THLD_STAT; + writel(reg, master->regs + INTR_STATUS_EN); + + reg = readl(master->regs + INTR_SIGNAL_EN); + reg &= ~INTR_IBI_THLD_STAT; + if (enable) + reg |= INTR_IBI_THLD_STAT; + writel(reg, master->regs + INTR_SIGNAL_EN); +} + static void dw_i3c_master_set_sir_enabled(struct dw_i3c_master *master, struct i3c_dev_desc *dev, u8 idx, bool enable) @@ -1170,23 +1187,34 @@ static void dw_i3c_master_set_sir_enabled(struct dw_i3c_master *master, } writel(reg, master->regs + IBI_SIR_REQ_REJECT); - if (global) { - reg = readl(master->regs + INTR_STATUS_EN); - reg &= ~INTR_IBI_THLD_STAT; - if (enable) - reg |= INTR_IBI_THLD_STAT; - writel(reg, master->regs + INTR_STATUS_EN); - - reg = readl(master->regs + INTR_SIGNAL_EN); - reg &= ~INTR_IBI_THLD_STAT; - if (enable) - reg |= INTR_IBI_THLD_STAT; - writel(reg, master->regs + INTR_SIGNAL_EN); - } + if (global) + dw_i3c_master_enable_sir_signal(master, enable); + spin_unlock_irqrestore(&master->devs_lock, flags); } +static int dw_i3c_master_enable_hotjoin(struct i3c_master_controller *m) +{ + struct dw_i3c_master *master = to_dw_i3c_master(m); + + dw_i3c_master_enable_sir_signal(master, true); + writel(readl(master->regs + DEVICE_CTRL) & ~DEV_CTRL_HOT_JOIN_NACK, + master->regs + DEVICE_CTRL); + + return 0; +} + +static int dw_i3c_master_disable_hotjoin(struct i3c_master_controller *m) +{ + struct dw_i3c_master *master = to_dw_i3c_master(m); + + writel(readl(master->regs + DEVICE_CTRL) | DEV_CTRL_HOT_JOIN_NACK, + master->regs + DEVICE_CTRL); + + return 0; +} + static int dw_i3c_master_enable_ibi(struct i3c_dev_desc *dev) { struct dw_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev); @@ -1326,6 +1354,8 @@ static void dw_i3c_master_irq_handle_ibis(struct dw_i3c_master *master) if (IBI_TYPE_SIRQ(reg)) { dw_i3c_master_handle_ibi_sir(master, reg); + } else if (IBI_TYPE_HJ(reg)) { + queue_work(master->base.wq, &master->hj_work); } else { len = IBI_QUEUE_STATUS_DATA_LEN(reg); dev_info(&master->base.dev, @@ -1393,6 +1423,8 @@ static const struct i3c_master_controller_ops dw_mipi_i3c_ibi_ops = { .enable_ibi = dw_i3c_master_enable_ibi, .disable_ibi = dw_i3c_master_disable_ibi, .recycle_ibi_slot = dw_i3c_master_recycle_ibi_slot, + .enable_hotjoin = dw_i3c_master_enable_hotjoin, + .disable_hotjoin = dw_i3c_master_disable_hotjoin, }; /* default platform ops implementations */ @@ -1412,6 +1444,14 @@ static const struct dw_i3c_platform_ops dw_i3c_platform_ops_default = { .set_dat_ibi = dw_i3c_platform_set_dat_ibi_nop, }; +static void dw_i3c_hj_work(struct work_struct *work) +{ + struct dw_i3c_master *master = + container_of(work, typeof(*master), hj_work); + + i3c_master_do_daa(&master->base); +} + int dw_i3c_common_probe(struct dw_i3c_master *master, struct platform_device *pdev) { @@ -1469,6 +1509,7 @@ int dw_i3c_common_probe(struct dw_i3c_master *master, if (master->ibi_capable) ops = &dw_mipi_i3c_ibi_ops; + INIT_WORK(&master->hj_work, dw_i3c_hj_work); ret = i3c_master_register(&master->base, &pdev->dev, ops, false); if (ret) goto err_assert_rst; diff --git a/drivers/i3c/master/dw-i3c-master.h b/drivers/i3c/master/dw-i3c-master.h index ab862c5d15fe..4ab94aa72252 100644 --- a/drivers/i3c/master/dw-i3c-master.h +++ b/drivers/i3c/master/dw-i3c-master.h @@ -57,6 +57,8 @@ struct dw_i3c_master { /* platform-specific data */ const struct dw_i3c_platform_ops *platform_ops; + + struct work_struct hj_work; }; struct dw_i3c_platform_ops { diff --git a/drivers/i3c/master/svc-i3c-master.c b/drivers/i3c/master/svc-i3c-master.c index 5ee4db68988e..bb299ce02ccc 100644 --- a/drivers/i3c/master/svc-i3c-master.c +++ b/drivers/i3c/master/svc-i3c-master.c @@ -415,6 +415,19 @@ static void svc_i3c_master_ibi_work(struct work_struct *work) int ret; mutex_lock(&master->lock); + /* + * IBIWON may be set before SVC_I3C_MCTRL_REQUEST_AUTO_IBI, causing + * readl_relaxed_poll_timeout() to return immediately. Consequently, + * ibitype will be 0 since it was last updated only after the 8th SCL + * cycle, leading to missed client IBI handlers. + * + * A typical scenario is when IBIWON occurs and bus arbitration is lost + * at svc_i3c_master_priv_xfers(). + * + * Clear SVC_I3C_MINT_IBIWON before sending SVC_I3C_MCTRL_REQUEST_AUTO_IBI. + */ + writel(SVC_I3C_MINT_IBIWON, master->regs + SVC_I3C_MSTATUS); + /* Acknowledge the incoming interrupt with the AUTOIBI mechanism */ writel(SVC_I3C_MCTRL_REQUEST_AUTO_IBI | SVC_I3C_MCTRL_IBIRESP_AUTO, @@ -429,9 +442,6 @@ static void svc_i3c_master_ibi_work(struct work_struct *work) goto reenable_ibis; } - /* Clear the interrupt status */ - writel(SVC_I3C_MINT_IBIWON, master->regs + SVC_I3C_MSTATUS); - status = readl(master->regs + SVC_I3C_MSTATUS); ibitype = SVC_I3C_MSTATUS_IBITYPE(status); ibiaddr = SVC_I3C_MSTATUS_IBIADDR(status); @@ -1080,7 +1090,7 @@ static int svc_i3c_master_xfer(struct svc_i3c_master *master, * and yield the above events handler. */ if (SVC_I3C_MSTATUS_IBIWON(reg)) { - ret = -ENXIO; + ret = -EAGAIN; *actual_len = 0; goto emit_stop; } |