summaryrefslogtreecommitdiff
path: root/drivers/i3c
diff options
context:
space:
mode:
authorOleksandr Shulzhenko <oleksandr.shulzhenko.viktorovych@intel.com>2021-09-09 16:05:26 +0300
committerOleksandr Shulzhenko <oleksandr.shulzhenko.viktorovych@intel.com>2021-11-18 13:02:55 +0300
commitee31c3564a2599abc9d3c30d964f0a429220fafc (patch)
tree7ad5939b462fd4fdc0f8418c7d7e6360f6220d76 /drivers/i3c
parentc7981259e359523e74044f926b69a804c61c86b1 (diff)
downloadlinux-ee31c3564a2599abc9d3c30d964f0a429220fafc.tar.xz
i3c: master: dw: add IBI support
i3c_master_controller_ops APIs were implemented in order to provide i3c drivers with interfaces to act upon IBI requests sent to dw i3c master devices The ASPEED implementation was used as a reference: https://github.com/AspeedTech-BMC/linux Change-Id: I4de28caffc10e099feaecc68e943e1a85224b346 Signed-off-by: Oleksandr Shulzhenko <oleksandr.shulzhenko.viktorovych@intel.com>
Diffstat (limited to 'drivers/i3c')
-rw-r--r--drivers/i3c/master/dw-i3c-master.c294
1 files changed, 288 insertions, 6 deletions
diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c
index a70dc8d02c89..a026b7e6dca6 100644
--- a/drivers/i3c/master/dw-i3c-master.c
+++ b/drivers/i3c/master/dw-i3c-master.c
@@ -76,6 +76,17 @@
#define RX_TX_DATA_PORT 0x14
#define IBI_QUEUE_STATUS 0x18
+#define IBI_QUEUE_STATUS_RSP_NACK BIT(31)
+#define IBI_QUEUE_STATUS_PEC_ERR BIT(30)
+#define IBI_QUEUE_STATUS_IBI_ID(x) (((x) & GENMASK(15, 8)) >> 8)
+#define IBI_QUEUE_STATUS_DATA_LEN(x) ((x) & GENMASK(7, 0))
+
+#define IBI_QUEUE_IBI_ADDR(x) (IBI_QUEUE_STATUS_IBI_ID(x) >> 1)
+#define IBI_QUEUE_IBI_RNW(x) (IBI_QUEUE_STATUS_IBI_ID(x) & BIT(0))
+#define IBI_TYPE_SIR(x) \
+ ({ typeof(x) x_ = (x); \
+ (IBI_QUEUE_IBI_ADDR(x_) != I3C_HOT_JOIN_ADDR) && IBI_QUEUE_IBI_RNW(x_); })
+
#define IBI_QUEUE_DATA 0x18
#define IBI_QUEUE_DATA_STATUS_MASK GENMASK(31, 28)
#define IBI_QUEUE_DATA_PAYLOAD_MASK GENMASK(15, 8)
@@ -135,7 +146,8 @@
INTR_TX_THLD_STAT | \
INTR_RX_THLD_STAT)
#define INTR_MASTER_MASK (INTR_TRANSFER_ERR_STAT | \
- INTR_RESP_READY_STAT)
+ INTR_RESP_READY_STAT | \
+ INTR_IBI_THLD_STAT)
#define QUEUE_STATUS_LEVEL 0x4c
#define QUEUE_STATUS_IBI_STATUS_CNT(x) (((x) & GENMASK(28, 24)) >> 24)
@@ -193,6 +205,10 @@
#define SLAVE_CONFIG 0xec
#define DEV_ADDR_TABLE_LEGACY_I2C_DEV BIT(31)
+#define DEV_ADDR_TABLE_DEV_NACK_RETRY(x) (((x) << 29) & GENMASK(30, 29))
+#define DEV_ADDR_TABLE_MR_REJECT BIT(14)
+#define DEV_ADDR_TABLE_SIR_REJECT BIT(13)
+#define DEV_ADDR_TABLE_IBI_WITH_DATA BIT(12)
#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_LOC(start, idx) ((start) + ((idx) << 2))
@@ -247,6 +263,14 @@ struct dw_i3c_master {
struct dw_i3c_xfer *cur;
spinlock_t lock;
} xferqueue;
+ struct {
+ struct i3c_dev_desc *slots[MAX_DEVS];
+ /*
+ * Prevents simultaneous access to IBI related registers
+ * and slots array.
+ */
+ spinlock_t lock;
+ } ibi;
struct dw_i3c_master_caps caps;
void __iomem *regs;
struct reset_control *core_rst;
@@ -258,6 +282,8 @@ struct dw_i3c_master {
struct dw_i3c_i2c_dev_data {
u8 index;
+ s8 ibi;
+ struct i3c_generic_ibi_pool *ibi_pool;
};
static u8 even_parity(u8 p)
@@ -358,18 +384,39 @@ static void dw_i3c_master_wr_tx_fifo(struct dw_i3c_master *master,
}
}
-static void dw_i3c_master_read_rx_fifo(struct dw_i3c_master *master,
- u8 *bytes, int nbytes)
+static void dw_i3c_master_read_fifo(struct dw_i3c_master *master, u32 fifo_reg,
+ u8 *bytes, int nbytes)
{
- readsl(master->regs + RX_TX_DATA_PORT, bytes, nbytes / 4);
+ readsl(master->regs + fifo_reg, bytes, nbytes / 4);
if (nbytes & 3) {
u32 tmp;
- readsl(master->regs + RX_TX_DATA_PORT, &tmp, 1);
+ readsl(master->regs + fifo_reg, &tmp, 1);
memcpy(bytes + (nbytes & ~3), &tmp, nbytes & 3);
}
}
+static void dw_i3c_master_read_rx_fifo(struct dw_i3c_master *master,
+ u8 *bytes, int nbytes)
+{
+ dw_i3c_master_read_fifo(master, RX_TX_DATA_PORT, bytes, nbytes);
+}
+
+static void dw_i3c_master_read_ibi_fifo(struct dw_i3c_master *master,
+ u8 *bytes, int nbytes)
+{
+ dw_i3c_master_read_fifo(master, IBI_QUEUE_DATA, bytes, nbytes);
+}
+
+static void dw_i3c_master_flush_ibi_fifo(struct dw_i3c_master *master, int nbytes)
+{
+ int nwords = (nbytes + 3) >> 2;
+ int i;
+
+ for (i = 0; i < nwords; i++)
+ readl(master->regs + IBI_QUEUE_DATA);
+}
+
static struct dw_i3c_xfer *
dw_i3c_master_alloc_xfer(struct dw_i3c_master *master, unsigned int ncmds)
{
@@ -689,6 +736,12 @@ static int dw_i3c_master_bus_init(struct i3c_master_controller *m)
writel(INTR_MASTER_MASK, master->regs + INTR_STATUS_EN);
writel(INTR_MASTER_MASK, master->regs + INTR_SIGNAL_EN);
+ thld_ctrl = readl(master->regs + QUEUE_THLD_CTRL);
+ thld_ctrl &= ~(QUEUE_THLD_CTRL_IBI_STA_MASK | QUEUE_THLD_CTRL_IBI_DAT_MASK);
+ thld_ctrl |= QUEUE_THLD_CTRL_IBI_STA(1);
+ thld_ctrl |= QUEUE_THLD_CTRL_IBI_DAT(1);
+ writel(thld_ctrl, master->regs + QUEUE_THLD_CTRL);
+
ret = i3c_master_get_free_addr(m, 0);
if (ret < 0)
return ret;
@@ -703,7 +756,6 @@ static int dw_i3c_master_bus_init(struct i3c_master_controller *m)
if (ret)
return ret;
- writel(IBI_REQ_REJECT_ALL, master->regs + IBI_SIR_REQ_REJECT);
writel(IBI_REQ_REJECT_ALL, master->regs + IBI_MR_REQ_REJECT);
/* For now don't support Hot-Join */
@@ -1203,6 +1255,142 @@ static int dw_i3c_master_i2c_xfers(struct i2c_dev_desc *dev,
return ret;
}
+static int dw_i3c_master_request_ibi(struct i3c_dev_desc *dev,
+ const struct i3c_ibi_setup *req)
+{
+ struct i3c_master_controller *m = i3c_dev_get_master(dev);
+ struct dw_i3c_master *master = to_dw_i3c_master(m);
+ struct dw_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
+ unsigned int i;
+
+ data->ibi_pool = i3c_generic_ibi_alloc_pool(dev, req);
+ if (IS_ERR(data->ibi_pool))
+ return PTR_ERR(data->ibi_pool);
+
+ spin_lock_irq(&master->ibi.lock);
+ for (i = 0; i < master->maxdevs; i++) {
+ if (!master->ibi.slots[i]) {
+ data->ibi = i;
+ master->ibi.slots[i] = dev;
+ break;
+ }
+ }
+ spin_unlock_irq(&master->ibi.lock);
+
+ if (i >= master->maxdevs) {
+ i3c_generic_ibi_free_pool(data->ibi_pool);
+ data->ibi_pool = NULL;
+ return -ENOSPC;
+ }
+
+ return 0;
+}
+
+static int dw_i3c_master_enable_ibi(struct i3c_dev_desc *dev)
+{
+ struct i3c_master_controller *m = i3c_dev_get_master(dev);
+ struct dw_i3c_master *master = to_dw_i3c_master(m);
+ int ret, pos, dat_loc;
+ u32 reg;
+
+ pos = dw_i3c_master_get_addr_pos(master, dev->info.dyn_addr);
+ if (pos < 0)
+ return pos;
+
+ /*
+ * Clean-up the bit in IBI_SIR_REQ_REJECT so that the SIR request from the specific
+ * slave device is acknowledged by the master device.
+ */
+ spin_lock_irq(&master->ibi.lock);
+ reg = readl(master->regs + IBI_SIR_REQ_REJECT) & ~BIT(dev->info.dyn_addr);
+ writel(reg, master->regs + IBI_SIR_REQ_REJECT);
+
+ /*
+ * Corresponding changes to DAT: ACK the SIR from the specific device;
+ * One or more data bytes must be present.
+ */
+ dat_loc = DEV_ADDR_TABLE_LOC(master->datstartaddr, pos);
+ reg = readl(master->regs + dat_loc);
+ reg &= ~DEV_ADDR_TABLE_SIR_REJECT;
+ reg |= DEV_ADDR_TABLE_IBI_WITH_DATA;
+ writel(reg, master->regs + dat_loc);
+
+ spin_unlock_irq(&master->ibi.lock);
+
+ /* Enable SIR generation on the requested slave device */
+ ret = i3c_master_enec_locked(m, dev->info.dyn_addr, I3C_CCC_EVENT_SIR);
+ if (ret) {
+ spin_lock_irq(&master->ibi.lock);
+ reg = readl(master->regs + IBI_SIR_REQ_REJECT);
+ reg |= BIT(dev->info.dyn_addr);
+ writel(reg, master->regs + IBI_SIR_REQ_REJECT);
+
+ reg = readl(master->regs + dat_loc);
+ reg |= DEV_ADDR_TABLE_SIR_REJECT;
+ reg &= ~DEV_ADDR_TABLE_IBI_WITH_DATA;
+ writel(reg, master->regs + dat_loc);
+ spin_unlock_irq(&master->ibi.lock);
+ }
+
+ return ret;
+}
+
+static int dw_i3c_master_disable_ibi(struct i3c_dev_desc *dev)
+{
+ struct i3c_master_controller *m = i3c_dev_get_master(dev);
+ struct dw_i3c_master *master = to_dw_i3c_master(m);
+ u32 reg;
+ int ret, pos, dat_loc;
+
+ /* Disable SIR generation on the requested slave device */
+ ret = i3c_master_disec_locked(m, dev->info.dyn_addr, I3C_CCC_EVENT_SIR);
+ if (ret)
+ return ret;
+
+ pos = dw_i3c_master_get_addr_pos(master, dev->info.dyn_addr);
+ if (pos < 0) {
+ dev_warn(master->dev, "Failed to get DAT addr pos for dev %02x\n",
+ dev->info.dyn_addr);
+ return pos;
+ }
+
+ spin_lock_irq(&master->ibi.lock);
+ reg = readl(master->regs + IBI_SIR_REQ_REJECT);
+ reg |= BIT(dev->info.dyn_addr);
+ writel(reg, master->regs + IBI_SIR_REQ_REJECT);
+
+ dat_loc = DEV_ADDR_TABLE_LOC(master->datstartaddr, pos);
+ reg = readl(master->regs + dat_loc);
+ reg |= DEV_ADDR_TABLE_SIR_REJECT;
+ reg &= ~DEV_ADDR_TABLE_IBI_WITH_DATA;
+ writel(reg, master->regs + dat_loc);
+ spin_unlock_irq(&master->ibi.lock);
+
+ return 0;
+}
+
+static void dw_i3c_master_free_ibi(struct i3c_dev_desc *dev)
+{
+ struct i3c_master_controller *m = i3c_dev_get_master(dev);
+ struct dw_i3c_master *master = to_dw_i3c_master(m);
+ struct dw_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
+
+ spin_lock_irq(&master->ibi.lock);
+ master->ibi.slots[data->ibi] = NULL;
+ data->ibi = -1;
+ spin_unlock_irq(&master->ibi.lock);
+
+ i3c_generic_ibi_free_pool(data->ibi_pool);
+}
+
+static void dw_i3c_master_recycle_ibi_slot(struct i3c_dev_desc *dev,
+ struct i3c_ibi_slot *slot)
+{
+ struct dw_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
+
+ i3c_generic_ibi_recycle_slot(data->ibi_pool, slot);
+}
+
static int dw_i3c_master_attach_i2c_dev(struct i2c_dev_desc *dev)
{
struct i3c_master_controller *m = i2c_dev_get_master(dev);
@@ -1247,6 +1435,90 @@ static void dw_i3c_master_detach_i2c_dev(struct i2c_dev_desc *dev)
kfree(data);
}
+static struct i3c_dev_desc *dw_get_i3c_dev_by_addr(struct dw_i3c_master *master,
+ u8 addr)
+{
+ int i;
+
+ for (i = 0; i < master->maxdevs; i++) {
+ if (master->ibi.slots[i] && master->ibi.slots[i]->info.dyn_addr == addr)
+ return master->ibi.slots[i];
+ }
+ return NULL;
+}
+
+static void dw_i3c_master_sir_handler(struct dw_i3c_master *master,
+ u32 ibi_status)
+{
+ u8 length = IBI_QUEUE_STATUS_DATA_LEN(ibi_status);
+ u8 addr = IBI_QUEUE_IBI_ADDR(ibi_status);
+ struct dw_i3c_i2c_dev_data *data;
+ struct i3c_ibi_slot *slot;
+ struct i3c_dev_desc *dev;
+ u8 *buf;
+ int i;
+
+ dev = dw_get_i3c_dev_by_addr(master, addr);
+ if (!dev) {
+ dev_warn(master->dev, "no matching dev\n");
+ goto err;
+ }
+
+ data = i3c_dev_get_master_data(dev);
+ slot = i3c_generic_ibi_get_free_slot(data->ibi_pool);
+ if (!slot) {
+ dev_warn(master->dev, "no free ibi slot\n");
+ goto err;
+ }
+ buf = slot->data;
+ /* prepend ibi status */
+ memcpy(buf, &ibi_status, sizeof(ibi_status));
+ buf += sizeof(ibi_status);
+
+ for (i = 0; i < (!(length % 4) ? (length / 4) : (length / 4 + 1)); i++)
+ dw_i3c_master_read_ibi_fifo(master, buf, length);
+
+ slot->len = length + sizeof(ibi_status);
+
+ i3c_master_queue_ibi(dev, slot);
+ return;
+
+err:
+ dw_i3c_master_flush_ibi_fifo(master, length);
+}
+
+static void dw_i3c_master_demux_ibis(struct dw_i3c_master *master)
+{
+ u32 nibi, status, intr_signal_en;
+ int i;
+
+ nibi = QUEUE_STATUS_IBI_STATUS_CNT(readl(master->regs + QUEUE_STATUS_LEVEL));
+
+ spin_lock(&master->ibi.lock);
+ intr_signal_en = readl(master->regs + INTR_SIGNAL_EN);
+ intr_signal_en &= ~INTR_IBI_THLD_STAT;
+ writel(intr_signal_en, master->regs + INTR_SIGNAL_EN);
+
+ for (i = 0; i < nibi; i++) {
+ status = readl(master->regs + IBI_QUEUE_STATUS);
+
+ if (status & IBI_QUEUE_STATUS_RSP_NACK)
+ dev_warn_once(master->dev, "ibi from unrecognized slave %02lx\n",
+ IBI_QUEUE_IBI_ADDR(status));
+
+ if (status & IBI_QUEUE_STATUS_PEC_ERR)
+ dev_warn(master->dev, "ibi crc/pec error\n");
+
+ if (IBI_TYPE_SIR(status))
+ dw_i3c_master_sir_handler(master, status);
+ }
+
+ intr_signal_en = readl(master->regs + INTR_SIGNAL_EN);
+ intr_signal_en |= INTR_IBI_THLD_STAT;
+ writel(intr_signal_en, master->regs + INTR_SIGNAL_EN);
+ spin_unlock(&master->ibi.lock);
+}
+
static irqreturn_t dw_i3c_master_irq_handler(int irq, void *dev_id)
{
struct dw_i3c_master *master = dev_id;
@@ -1265,6 +1537,9 @@ static irqreturn_t dw_i3c_master_irq_handler(int irq, void *dev_id)
writel(INTR_TRANSFER_ERR_STAT, master->regs + INTR_STATUS);
spin_unlock(&master->xferqueue.lock);
+ if (status & INTR_IBI_THLD_STAT)
+ dw_i3c_master_demux_ibis(master);
+
return IRQ_HANDLED;
}
@@ -1281,6 +1556,11 @@ static const struct i3c_master_controller_ops dw_mipi_i3c_ops = {
.attach_i2c_dev = dw_i3c_master_attach_i2c_dev,
.detach_i2c_dev = dw_i3c_master_detach_i2c_dev,
.i2c_xfers = dw_i3c_master_i2c_xfers,
+ .request_ibi = dw_i3c_master_request_ibi,
+ .enable_ibi = dw_i3c_master_enable_ibi,
+ .free_ibi = dw_i3c_master_free_ibi,
+ .disable_ibi = dw_i3c_master_disable_ibi,
+ .recycle_ibi_slot = dw_i3c_master_recycle_ibi_slot,
};
static int dw_i3c_probe(struct platform_device *pdev)
@@ -1314,6 +1594,8 @@ static int dw_i3c_probe(struct platform_device *pdev)
spin_lock_init(&master->xferqueue.lock);
INIT_LIST_HEAD(&master->xferqueue.list);
+ spin_lock_init(&master->ibi.lock);
+
platform_set_drvdata(pdev, master);
/* Information regarding the FIFOs/QUEUEs depth */