Commit 4286e1fc authored by Linus Torvalds's avatar Linus Torvalds
Browse files
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
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -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,
+6 −0
Original line number Diff line number Diff line
@@ -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);
+54 −13
Original line number Diff line number Diff line
@@ -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);
	if (global)
		dw_i3c_master_enable_sir_signal(master, enable);

		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);
	}

	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;
+2 −0
Original line number Diff line number Diff line
@@ -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 {
+14 −4
Original line number Diff line number Diff line
@@ -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;
	}