summaryrefslogtreecommitdiffstats
path: root/drivers/i3c/master
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/i3c/master')
-rw-r--r--drivers/i3c/master/dw-i3c-master.c67
-rw-r--r--drivers/i3c/master/dw-i3c-master.h2
-rw-r--r--drivers/i3c/master/mipi-i3c-hci/core.c8
-rw-r--r--drivers/i3c/master/svc-i3c-master.c18
4 files changed, 78 insertions, 17 deletions
diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c
index 276153e10f..0ec00e644b 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 ab862c5d15..4ab94aa722 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/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c
index d7e966a255..4e7d6a43ee 100644
--- a/drivers/i3c/master/mipi-i3c-hci/core.c
+++ b/drivers/i3c/master/mipi-i3c-hci/core.c
@@ -631,6 +631,7 @@ static irqreturn_t i3c_hci_irq_handler(int irq, void *dev_id)
static int i3c_hci_init(struct i3c_hci *hci)
{
u32 regval, offset;
+ bool size_in_dwords;
int ret;
/* Validate HCI hardware version */
@@ -654,11 +655,16 @@ static int i3c_hci_init(struct i3c_hci *hci)
hci->caps = reg_read(HC_CAPABILITIES);
DBG("caps = %#x", hci->caps);
+ size_in_dwords = hci->version_major < 1 ||
+ (hci->version_major == 1 && hci->version_minor < 1);
+
regval = reg_read(DAT_SECTION);
offset = FIELD_GET(DAT_TABLE_OFFSET, regval);
hci->DAT_regs = offset ? hci->base_regs + offset : NULL;
hci->DAT_entries = FIELD_GET(DAT_TABLE_SIZE, regval);
hci->DAT_entry_size = FIELD_GET(DAT_ENTRY_SIZE, regval) ? 0 : 8;
+ if (size_in_dwords)
+ hci->DAT_entries = 4 * hci->DAT_entries / hci->DAT_entry_size;
dev_info(&hci->master.dev, "DAT: %u %u-bytes entries at offset %#x\n",
hci->DAT_entries, hci->DAT_entry_size, offset);
@@ -667,6 +673,8 @@ static int i3c_hci_init(struct i3c_hci *hci)
hci->DCT_regs = offset ? hci->base_regs + offset : NULL;
hci->DCT_entries = FIELD_GET(DCT_TABLE_SIZE, regval);
hci->DCT_entry_size = FIELD_GET(DCT_ENTRY_SIZE, regval) ? 0 : 16;
+ if (size_in_dwords)
+ hci->DCT_entries = 4 * hci->DCT_entries / hci->DCT_entry_size;
dev_info(&hci->master.dev, "DCT: %u %u-bytes entries at offset %#x\n",
hci->DCT_entries, hci->DCT_entry_size, offset);
diff --git a/drivers/i3c/master/svc-i3c-master.c b/drivers/i3c/master/svc-i3c-master.c
index 5ee4db6898..bb299ce02c 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;
}