Commit 4286e1fc authored by Linus Torvalds's avatar Linus Torvalds

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()
parents 6951abe8 1d083260
......@@ -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,
......
......@@ -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);
......
......@@ -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;
......
......@@ -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 {
......
......@@ -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;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment