summaryrefslogtreecommitdiff
path: root/drivers/i3c/master/dw-i3c-master.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/i3c/master/dw-i3c-master.c')
-rw-r--r--drivers/i3c/master/dw-i3c-master.c121
1 files changed, 87 insertions, 34 deletions
diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c
index 889e2ed5bc83..7eb09ad10171 100644
--- a/drivers/i3c/master/dw-i3c-master.c
+++ b/drivers/i3c/master/dw-i3c-master.c
@@ -5,6 +5,7 @@
* Author: Vitor Soares <vitor.soares@synopsys.com>
*/
+#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/completion.h>
@@ -204,12 +205,23 @@
#define EXTENDED_CAPABILITY 0xe8
#define SLAVE_CONFIG 0xec
+#define DYN_ADDR_LO_MASK GENMASK(4, 0)
+#define DYN_ADDR_HI_MASK GENMASK(6, 5)
+#define IBI_SIR_BIT_MOD 32 /* 32-bit vector */
+
+#define DW_I3C_DEV_NACK_RETRY_CNT_MAX 0x3
+#define DEV_ADDR_TABLE_DEV_NACK_RETRY_MASK GENMASK(30, 29)
+#define DEV_ADDR_TABLE_DYNAMIC_MASK GENMASK(23, 16)
+#define DEV_ADDR_TABLE_STATIC_MASK GENMASK(6, 0)
#define DEV_ADDR_TABLE_IBI_MDB BIT(12)
#define DEV_ADDR_TABLE_SIR_REJECT BIT(13)
+#define DEV_ADDR_TABLE_DEV_NACK_RETRY_CNT(x) \
+ FIELD_PREP(DEV_ADDR_TABLE_DEV_NACK_RETRY_MASK, (x))
#define DEV_ADDR_TABLE_LEGACY_I2C_DEV BIT(31)
-#define DEV_ADDR_TABLE_DYNAMIC_ADDR(x) (((x) << 16) & GENMASK(23, 16))
-#define DEV_ADDR_TABLE_STATIC_ADDR(x) ((x) & GENMASK(6, 0))
+#define DEV_ADDR_TABLE_DYNAMIC_ADDR(x) FIELD_PREP(DEV_ADDR_TABLE_DYNAMIC_MASK, x)
+#define DEV_ADDR_TABLE_STATIC_ADDR(x) FIELD_PREP(DEV_ADDR_TABLE_STATIC_MASK, x)
#define DEV_ADDR_TABLE_LOC(start, idx) ((start) + ((idx) << 2))
+#define DEV_ADDR_TABLE_GET_DYNAMIC_ADDR(x) FIELD_GET(DEV_ADDR_TABLE_DYNAMIC_MASK, x)
#define I3C_BUS_SDR1_SCL_RATE 8000000
#define I3C_BUS_SDR2_SCL_RATE 6000000
@@ -257,6 +269,14 @@ struct dw_i3c_drvdata {
u32 flags;
};
+static inline u32 get_ibi_sir_bit_index(u8 addr)
+{
+ u32 lo = FIELD_GET(DYN_ADDR_LO_MASK, addr);
+ u32 hi = FIELD_GET(DYN_ADDR_HI_MASK, addr);
+
+ return (lo + hi) % IBI_SIR_BIT_MOD;
+}
+
static bool dw_i3c_master_supports_ccc_cmd(struct i3c_master_controller *m,
const struct i3c_ccc_cmd *cmd)
{
@@ -409,17 +429,14 @@ static void dw_i3c_master_start_xfer_locked(struct dw_i3c_master *master)
static void dw_i3c_master_enqueue_xfer(struct dw_i3c_master *master,
struct dw_i3c_xfer *xfer)
{
- unsigned long flags;
-
init_completion(&xfer->comp);
- spin_lock_irqsave(&master->xferqueue.lock, flags);
+ guard(spinlock_irqsave)(&master->xferqueue.lock);
if (master->xferqueue.cur) {
list_add_tail(&xfer->node, &master->xferqueue.list);
} else {
master->xferqueue.cur = xfer;
dw_i3c_master_start_xfer_locked(master);
}
- spin_unlock_irqrestore(&master->xferqueue.lock, flags);
}
static void dw_i3c_master_dequeue_xfer_locked(struct dw_i3c_master *master,
@@ -444,11 +461,8 @@ static void dw_i3c_master_dequeue_xfer_locked(struct dw_i3c_master *master,
static void dw_i3c_master_dequeue_xfer(struct dw_i3c_master *master,
struct dw_i3c_xfer *xfer)
{
- unsigned long flags;
-
- spin_lock_irqsave(&master->xferqueue.lock, flags);
+ guard(spinlock_irqsave)(&master->xferqueue.lock);
dw_i3c_master_dequeue_xfer_locked(master, xfer);
- spin_unlock_irqrestore(&master->xferqueue.lock, flags);
}
static void dw_i3c_master_end_xfer_locked(struct dw_i3c_master *master, u32 isr)
@@ -1099,6 +1113,7 @@ static int dw_i3c_master_i2c_xfers(struct i2c_dev_desc *dev,
dev_err(master->dev,
"<%s> cannot resume i3c bus master, err: %d\n",
__func__, ret);
+ dw_i3c_master_free_xfer(xfer);
return ret;
}
@@ -1187,15 +1202,13 @@ static int dw_i3c_master_request_ibi(struct i3c_dev_desc *dev,
struct dw_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
struct i3c_master_controller *m = i3c_dev_get_master(dev);
struct dw_i3c_master *master = to_dw_i3c_master(m);
- unsigned long flags;
data->ibi_pool = i3c_generic_ibi_alloc_pool(dev, req);
if (IS_ERR(data->ibi_pool))
return PTR_ERR(data->ibi_pool);
- spin_lock_irqsave(&master->devs_lock, flags);
+ guard(spinlock_irqsave)(&master->devs_lock);
master->devs[data->index].ibi_dev = dev;
- spin_unlock_irqrestore(&master->devs_lock, flags);
return 0;
}
@@ -1205,11 +1218,10 @@ static void dw_i3c_master_free_ibi(struct i3c_dev_desc *dev)
struct dw_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
struct i3c_master_controller *m = i3c_dev_get_master(dev);
struct dw_i3c_master *master = to_dw_i3c_master(m);
- unsigned long flags;
- spin_lock_irqsave(&master->devs_lock, flags);
- master->devs[data->index].ibi_dev = NULL;
- spin_unlock_irqrestore(&master->devs_lock, flags);
+ scoped_guard(spinlock_irqsave, &master->devs_lock) {
+ master->devs[data->index].ibi_dev = NULL;
+ }
i3c_generic_ibi_free_pool(data->ibi_pool);
data->ibi_pool = NULL;
@@ -1236,14 +1248,21 @@ static void dw_i3c_master_set_sir_enabled(struct dw_i3c_master *master,
struct i3c_dev_desc *dev,
u8 idx, bool enable)
{
- unsigned long flags;
u32 dat_entry, reg;
bool global;
+ u8 dynamic_addr;
dat_entry = DEV_ADDR_TABLE_LOC(master->datstartaddr, idx);
- spin_lock_irqsave(&master->devs_lock, flags);
+ guard(spinlock_irqsave)(&master->devs_lock);
reg = readl(master->regs + dat_entry);
+ dynamic_addr = DEV_ADDR_TABLE_GET_DYNAMIC_ADDR(reg);
+
+ if (!dynamic_addr)
+ dev_warn(master->dev,
+ "<%s> unassigned slave device, dynamic addr:%x\n",
+ __func__, dynamic_addr);
+
if (enable) {
reg &= ~DEV_ADDR_TABLE_SIR_REJECT;
if (dev->info.bcr & I3C_BCR_IBI_PAYLOAD)
@@ -1256,20 +1275,17 @@ static void dw_i3c_master_set_sir_enabled(struct dw_i3c_master *master,
if (enable) {
global = (master->sir_rej_mask == IBI_REQ_REJECT_ALL);
- master->sir_rej_mask &= ~BIT(idx);
+ master->sir_rej_mask &= ~BIT(get_ibi_sir_bit_index(dynamic_addr));
} else {
bool hj_rejected = !!(readl(master->regs + DEVICE_CTRL) & DEV_CTRL_HOT_JOIN_NACK);
- master->sir_rej_mask |= BIT(idx);
+ master->sir_rej_mask |= BIT(get_ibi_sir_bit_index(dynamic_addr));
global = (master->sir_rej_mask == IBI_REQ_REJECT_ALL) && hj_rejected;
}
writel(master->sir_rej_mask, master->regs + IBI_SIR_REQ_REJECT);
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)
@@ -1370,7 +1386,6 @@ static void dw_i3c_master_handle_ibi_sir(struct dw_i3c_master *master,
struct dw_i3c_i2c_dev_data *data;
struct i3c_ibi_slot *slot;
struct i3c_dev_desc *dev;
- unsigned long flags;
u8 addr, len;
int idx;
@@ -1388,7 +1403,7 @@ static void dw_i3c_master_handle_ibi_sir(struct dw_i3c_master *master,
* a new platform op to validate it.
*/
- spin_lock_irqsave(&master->devs_lock, flags);
+ guard(spinlock_irqsave)(&master->devs_lock);
idx = dw_i3c_master_get_addr_pos(master, addr);
if (idx < 0) {
dev_dbg_ratelimited(&master->base.dev,
@@ -1424,14 +1439,10 @@ static void dw_i3c_master_handle_ibi_sir(struct dw_i3c_master *master,
}
i3c_master_queue_ibi(dev, slot);
- spin_unlock_irqrestore(&master->devs_lock, flags);
-
return;
err_drain:
dw_i3c_master_drain_ibi_queue(master, len);
-
- spin_unlock_irqrestore(&master->devs_lock, flags);
}
/* "ibis": referring to In-Band Interrupts, and not
@@ -1489,6 +1500,40 @@ static irqreturn_t dw_i3c_master_irq_handler(int irq, void *dev_id)
return IRQ_HANDLED;
}
+static int dw_i3c_master_set_dev_nack_retry(struct i3c_master_controller *m,
+ unsigned long dev_nack_retry_cnt)
+{
+ struct dw_i3c_master *master = to_dw_i3c_master(m);
+ u32 reg;
+ int i;
+
+ if (dev_nack_retry_cnt > DW_I3C_DEV_NACK_RETRY_CNT_MAX) {
+ dev_err(&master->base.dev,
+ "Value %ld exceeds maximum %d\n",
+ dev_nack_retry_cnt, DW_I3C_DEV_NACK_RETRY_CNT_MAX);
+ return -ERANGE;
+ }
+
+ /*
+ * Update DAT entries for all currently attached devices.
+ * We directly iterate through the master's device array.
+ */
+ for (i = 0; i < master->maxdevs; i++) {
+ /* Skip free/empty slots */
+ if (master->free_pos & BIT(i))
+ continue;
+
+ reg = readl(master->regs +
+ DEV_ADDR_TABLE_LOC(master->datstartaddr, i));
+ reg &= ~DEV_ADDR_TABLE_DEV_NACK_RETRY_MASK;
+ reg |= DEV_ADDR_TABLE_DEV_NACK_RETRY_CNT(dev_nack_retry_cnt);
+ writel(reg, master->regs +
+ DEV_ADDR_TABLE_LOC(master->datstartaddr, i));
+ }
+
+ return 0;
+}
+
static const struct i3c_master_controller_ops dw_mipi_i3c_ops = {
.bus_init = dw_i3c_master_bus_init,
.bus_cleanup = dw_i3c_master_bus_cleanup,
@@ -1509,6 +1554,7 @@ static const struct i3c_master_controller_ops dw_mipi_i3c_ops = {
.recycle_ibi_slot = dw_i3c_master_recycle_ibi_slot,
.enable_hotjoin = dw_i3c_master_enable_hotjoin,
.disable_hotjoin = dw_i3c_master_disable_hotjoin,
+ .set_dev_nack_retry = dw_i3c_master_set_dev_nack_retry,
};
/* default platform ops implementations */
@@ -1570,6 +1616,8 @@ int dw_i3c_common_probe(struct dw_i3c_master *master,
spin_lock_init(&master->xferqueue.lock);
INIT_LIST_HEAD(&master->xferqueue.list);
+ spin_lock_init(&master->devs_lock);
+
writel(INTR_ALL, master->regs + INTR_STATUS);
irq = platform_get_irq(pdev, 0);
ret = devm_request_irq(&pdev->dev, irq,
@@ -1676,11 +1724,16 @@ static void dw_i3c_master_restore_addrs(struct dw_i3c_master *master)
if (master->free_pos & BIT(pos))
continue;
- if (master->devs[pos].is_i2c_addr)
- reg_val = DEV_ADDR_TABLE_LEGACY_I2C_DEV |
+ reg_val = readl(master->regs + DEV_ADDR_TABLE_LOC(master->datstartaddr, pos));
+
+ if (master->devs[pos].is_i2c_addr) {
+ reg_val &= ~DEV_ADDR_TABLE_STATIC_MASK;
+ reg_val |= DEV_ADDR_TABLE_LEGACY_I2C_DEV |
DEV_ADDR_TABLE_STATIC_ADDR(master->devs[pos].addr);
- else
- reg_val = DEV_ADDR_TABLE_DYNAMIC_ADDR(master->devs[pos].addr);
+ } else {
+ reg_val &= ~DEV_ADDR_TABLE_DYNAMIC_MASK;
+ reg_val |= DEV_ADDR_TABLE_DYNAMIC_ADDR(master->devs[pos].addr);
+ }
writel(reg_val, master->regs + DEV_ADDR_TABLE_LOC(master->datstartaddr, pos));
}