I3C for 6.10
Core: - Allow device driver to trigger controller runtime PM Drivers: - dw: hot-join support - svc: better IBI handling -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEBqsFVZXh8s/0O5JiY6TcMGxwOjIFAmZRluEACgkQY6TcMGxw OjJuEw//eCoRPLtAinSBRrPirmWyryVIhMOjZFK+8752Ycau7sl/RIp+8N6QiW8C cOLugoONPq90nlVOxwYq8jtF+qaPTBkSXNbsJox0qnuZrJsLjyF1CxIYUVMClQ5/ f7RGIhZv7paF26qP0li3ewQ8L0F13dCbdSzC2b9tI/Yy/GJmpzgpC6nv18YrlGK3 1+L6wmz4rhR18pbjZm8myMoa//NG0lkmXhAwPYF+X9n9IJufBNhsijW1akFmqbvE +8DVWGLDqaY4AnotD6XBLsbFQsPF+Z0F1oVO1cL45Ned92yBsnyEKbnkH0K5SLj+ FMvkiPGAOp5m1KxPLJ3XfthBUJp14SPufaJTsJo3tzQ5lE+4CMUkAD1Z3YTPqAZB HfkW48trejyVf8irUTv7NzZFQ62DPUREIHWcjnLASymgIqSWgiNCKHbufo61sU6u FoqMgZdcvsGmxz7Ld7RnVeU2hxaGqMNrc5jLC70YtrsFSkQB3bIfkHKhUSqeKdeO 3cD1pyNfgnqIJ4uhb3fG1bm/MkNcOpupTNlP+XVGBYTIpIy/HWfgPCyQlO9qCXGd ZBbp+FojyAYb9UTbXhAVJn9U8kvqcRHI5jRuYXmxJuptSfa2semoPtQ6Ism7n/2+ geHMAUs9s4KT670zzbv7FsPxma/b4mTcqpeCDxzjCQrNkGyfvDA= =EmUy -----END PGP SIGNATURE----- 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()
This commit is contained in:
commit
4286e1fceb
@ -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);
|
||||
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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user