summaryrefslogtreecommitdiff
path: root/drivers/i3c
diff options
context:
space:
mode:
authorIwona Winiarska <iwona.winiarska@intel.com>2022-02-20 01:39:51 +0300
committerIwona Winiarska <iwona.winiarska@intel.com>2022-04-12 14:07:34 +0300
commitcabd83ebe9d015c60012f5e78c340ac2b001ec98 (patch)
tree2d01202b996fbbe194cfa24ec86397e63016e7fc /drivers/i3c
parentbcc82f5f230e4ad36c928f0d2caf93eeb58aa64c (diff)
downloadlinux-cabd83ebe9d015c60012f5e78c340ac2b001ec98.tar.xz
i3c: master: dw: Implement I3C target functions
Add support for Synopsys DesignWare I3C Target functionality. Signed-off-by: Iwona Winiarska <iwona.winiarska@intel.com>
Diffstat (limited to 'drivers/i3c')
-rw-r--r--drivers/i3c/master/dw-i3c-master.c306
1 files changed, 271 insertions, 35 deletions
diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c
index 0752769ebedb..ce21b62e5be6 100644
--- a/drivers/i3c/master/dw-i3c-master.c
+++ b/drivers/i3c/master/dw-i3c-master.c
@@ -11,6 +11,7 @@
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/i3c/master.h>
+#include <linux/i3c/target.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/iopoll.h>
@@ -99,7 +100,8 @@
#define QUEUE_THLD_CTRL_RESP_BUF(x) (((x) - 1) << 8)
#define DATA_BUFFER_THLD_CTRL 0x20
-#define DATA_BUFFER_THLD_CTRL_RX_BUF GENMASK(11, 8)
+#define DATA_BUFFER_THLD_TX_START GENMASK(18, 16)
+#define DATA_BUFFER_THLD_CTRL_RX_BUF GENMASK(10, 8)
#define IBI_QUEUE_CTRL 0x24
#define IBI_MR_REQ_REJECT 0x2C
@@ -115,6 +117,8 @@
#define RESET_CTRL_SOFT BIT(0)
#define SLV_EVENT_CTRL 0x38
+#define SLV_EVENT_CTRL_SIR_EN BIT(0)
+
#define INTR_STATUS 0x3c
#define INTR_STATUS_EN 0x40
#define INTR_SIGNAL_EN 0x44
@@ -148,6 +152,11 @@
#define INTR_MASTER_MASK (INTR_TRANSFER_ERR_STAT | \
INTR_RESP_READY_STAT)
+#define INTR_TARGET_MASK (INTR_READ_REQ_RECV_STAT | \
+ INTR_RESP_READY_STAT | \
+ INTR_IBI_UPDATED_STAT | \
+ INTR_TRANSFER_ERR_STAT)
+
#define QUEUE_STATUS_LEVEL 0x4c
#define QUEUE_STATUS_IBI_STATUS_CNT(x) (((x) & GENMASK(28, 24)) >> 24)
#define QUEUE_STATUS_IBI_BUF_BLR(x) (((x) & GENMASK(23, 16)) >> 16)
@@ -165,14 +174,36 @@
#define DEV_CHAR_TABLE_POINTER 0x60
#define VENDOR_SPECIFIC_REG_POINTER 0x6c
+#define SLV_MIPI_ID_VALUE 0x70
#define SLV_PID_VALUE 0x74
+#define SLV_PID_HI(x) (((x) >> 32) & GENMASK(15, 0))
+#define SLV_PID_LO(x) ((x) & GENMASK(31, 0))
#define SLV_CHAR_CTRL 0x78
+#define SLV_DCR_MASK GENMASK(15, 8)
+#define SLV_DCR(x) (((x) << 8) & SLV_DCR_MASK)
+#define SLV_DEVICE_ROLE_MASK GENMASK(7, 6)
+#define SLV_DEVICE_ROLE(x) (((x) << 6) & SLV_DEVICE_ROLE_MASK)
+#define SLV_HDR_CAPABLE BIT(5)
+#define SLV_MAX_DATA_SPEED_LIMIT BIT(0)
+
#define SLV_MAX_LEN 0x7c
+#define SLV_MAX_RD_LEN(x) (((x) & GENMASK(31, 16)) >> 16)
+#define SLV_MAX_WR_LEN(x) ((x) & GENMASK(15, 0))
+
#define MAX_READ_TURNAROUND 0x80
#define MAX_DATA_SPEED 0x84
#define SLV_DEBUG_STATUS 0x88
#define SLV_INTR_REQ 0x8c
+#define SLV_INTR_REQ_IBI_STS(x) (((x) & GENMASK(9, 8)) >> 8)
+#define IBI_STS_ACCEPTED 0x01
+#define IBI_STS_NOT_ATTEMPTED 0x11
+
#define DEVICE_CTRL_EXTENDED 0xb0
+#define DEVICE_CTRL_EXTENDED_MODE_MASK GENMASK(1, 0)
+#define DEVICE_CTRL_EXTENDED_MODE(x) ((x) & DEVICE_CTRL_EXTENDED_MODE_MASK)
+#define DEV_OPERATION_MODE_CONTROLLER 0x00
+#define DEV_OPERATION_MODE_TARGET 0x01
+
#define SCL_I3C_OD_TIMING 0xb4
#define SCL_I3C_PP_TIMING 0xb8
#define SCL_I3C_TIMING_HCNT(x) (((x) << 16) & GENMASK(23, 16))
@@ -201,6 +232,8 @@
#define SDA_TX_HOLD_MAX 7
#define BUS_FREE_TIMING 0xd4
+#define BUS_AVAIL_TIME(x) (((x) << 16) & GENMASK(31, 16))
+#define MAX_BUS_AVAIL_CNT 0xffffU
#define BUS_I3C_MST_FREE(x) ((x) & GENMASK(15, 0))
#define BUS_IDLE_TIMING 0xd8
@@ -277,13 +310,18 @@ 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;
+ union {
+ struct {
+ struct i3c_dev_desc *slots[MAX_DEVS];
+ /*
+ * Prevents simultaneous access to IBI related registers
+ * and slots array.
+ */
+ spinlock_t lock;
+ } master;
+ struct {
+ struct completion comp;
+ } target;
} ibi;
struct dw_i3c_master_caps caps;
void __iomem *regs;
@@ -303,6 +341,11 @@ struct dw_i3c_master {
u32 i3c_pp_scl_high;
u32 sda_tx_hold;
} timings;
+ /* Used for handling private write */
+ struct {
+ void *buf;
+ u16 max_len;
+ } target_rx;
};
struct dw_i3c_i2c_dev_data {
@@ -955,13 +998,10 @@ static int dw_sda_tx_hold_cfg(struct dw_i3c_master *master)
return 0;
}
-static int dw_i3c_master_bus_init(struct i3c_master_controller *m)
+static int dw_i3c_bus_clk_cfg(struct i3c_master_controller *m)
{
struct dw_i3c_master *master = to_dw_i3c_master(m);
struct i3c_bus *bus = i3c_master_get_bus(m);
- u32 interrupt_mask = INTR_MASTER_MASK;
- struct i3c_device_info info = { };
- u32 thld_ctrl;
int ret;
switch (bus->mode) {
@@ -980,6 +1020,87 @@ static int dw_i3c_master_bus_init(struct i3c_master_controller *m)
return -EINVAL;
}
+ return 0;
+}
+
+static int dw_i3c_target_bus_init(struct i3c_master_controller *m)
+{
+ struct dw_i3c_master *master = to_dw_i3c_master(m);
+ struct i3c_dev_desc *desc = master->base.this;
+ void *rx_buf;
+ u32 reg;
+ int ret;
+
+ ret = dw_i3c_bus_clk_cfg(m);
+ if (ret)
+ return ret;
+
+ reg = readl(master->regs + SLV_MAX_LEN);
+ /*
+ * Set max private write length value based on read-only register.
+ * TODO: Handle updates after receiving SETMWL CCC.
+ */
+ master->target_rx.max_len = SLV_MAX_WR_LEN(reg);
+
+ rx_buf = kzalloc(master->target_rx.max_len, GFP_KERNEL);
+ if (!rx_buf)
+ return -ENOMEM;
+
+ master->target_rx.buf = rx_buf;
+
+ dw_i3c_master_disable(master);
+
+ reg = readl(master->regs + QUEUE_THLD_CTRL) & ~QUEUE_THLD_CTRL_RESP_BUF_MASK;
+ writel(reg, master->regs + QUEUE_THLD_CTRL);
+
+ reg = readl(master->regs + DATA_BUFFER_THLD_CTRL) & ~DATA_BUFFER_THLD_CTRL_RX_BUF;
+ writel(reg, master->regs + DATA_BUFFER_THLD_CTRL);
+
+ writel(INTR_ALL, master->regs + INTR_STATUS);
+ writel(INTR_TARGET_MASK, master->regs + INTR_STATUS_EN);
+ writel(INTR_TARGET_MASK, master->regs + INTR_SIGNAL_EN);
+
+ reg = readl(master->regs + DEVICE_CTRL_EXTENDED) & ~DEVICE_CTRL_EXTENDED_MODE_MASK;
+ reg |= DEVICE_CTRL_EXTENDED_MODE(DEV_OPERATION_MODE_TARGET);
+ writel(reg, master->regs + DEVICE_CTRL_EXTENDED);
+
+ writel(SLV_PID_LO(desc->info.pid), master->regs + SLV_PID_VALUE);
+ writel(SLV_PID_HI(desc->info.pid), master->regs + SLV_MIPI_ID_VALUE);
+
+ reg = readl(master->regs + SLV_CHAR_CTRL) & ~SLV_DCR_MASK & ~SLV_DEVICE_ROLE_MASK;
+ reg |= SLV_DCR(desc->info.dcr) | SLV_DEVICE_ROLE(0);
+ writel(reg, master->regs + SLV_CHAR_CTRL);
+
+ reg = readl(master->regs + BUS_FREE_TIMING) | BUS_AVAIL_TIME(MAX_BUS_AVAIL_CNT);
+ writel(reg, master->regs + BUS_FREE_TIMING);
+
+ dw_i3c_master_enable(master);
+
+ return 0;
+}
+
+static void dw_i3c_target_bus_cleanup(struct i3c_master_controller *m)
+{
+ struct dw_i3c_master *master = to_dw_i3c_master(m);
+
+ dw_i3c_master_disable(master);
+ kfree(master->target_rx.buf);
+}
+
+static int dw_i3c_master_bus_init(struct i3c_master_controller *m)
+{
+ struct dw_i3c_master *master = to_dw_i3c_master(m);
+ u32 interrupt_mask = INTR_MASTER_MASK;
+ struct i3c_device_info info = { };
+ u32 thld_ctrl;
+ int ret;
+
+ spin_lock_init(&master->ibi.master.lock);
+
+ ret = dw_i3c_bus_clk_cfg(m);
+ if (ret)
+ return ret;
+
ret = dw_sda_tx_hold_cfg(master);
if (ret)
return ret;
@@ -1378,6 +1499,72 @@ static int dw_i3c_master_priv_xfers(struct i3c_dev_desc *dev,
return ret;
}
+static int dw_i3c_target_priv_xfers(struct i3c_dev_desc *dev,
+ struct i3c_priv_xfer *i3c_xfers,
+ int i3c_nxfers)
+{
+ struct i3c_master_controller *m = i3c_dev_get_master(dev);
+ struct dw_i3c_master *master = to_dw_i3c_master(m);
+ struct dw_i3c_xfer *xfer;
+ int i;
+
+ if (!i3c_nxfers)
+ return 0;
+
+ xfer = dw_i3c_master_alloc_xfer(master, i3c_nxfers);
+ if (!xfer)
+ return -ENOMEM;
+
+ for (i = 0; i < i3c_nxfers; i++) {
+ struct dw_i3c_cmd *cmd = &xfer->cmds[i];
+
+ if (!i3c_xfers[i].rnw) {
+ cmd->tx_buf = i3c_xfers[i].data.out;
+ cmd->tx_len = i3c_xfers[i].len;
+ cmd->cmd_lo = 0 | (i << 3) | (cmd->tx_len << 16);
+
+ dw_i3c_master_wr_tx_fifo(master, cmd->tx_buf, cmd->tx_len);
+ writel(cmd->cmd_lo, master->regs + COMMAND_QUEUE_PORT);
+ }
+ }
+
+ dw_i3c_master_free_xfer(xfer);
+
+ return 0;
+}
+
+static int dw_i3c_target_generate_ibi(struct i3c_dev_desc *dev, const u8 *data, int len)
+{
+ struct i3c_master_controller *m = i3c_dev_get_master(dev);
+ struct dw_i3c_master *master = to_dw_i3c_master(m);
+ u32 reg;
+
+ if (data || len != 0)
+ return -EOPNOTSUPP;
+
+ reg = readl(master->regs + SLV_EVENT_CTRL);
+ if ((reg & SLV_EVENT_CTRL_SIR_EN) == 0)
+ return -EPERM;
+
+ init_completion(&master->ibi.target.comp);
+ writel(1, master->regs + SLV_INTR_REQ);
+
+ if (!wait_for_completion_timeout(&master->ibi.target.comp, XFER_TIMEOUT)) {
+ pr_warn("timeout waiting for completion\n");
+ return -EINVAL;
+ }
+
+ reg = readl(master->regs + SLV_INTR_REQ);
+ if (SLV_INTR_REQ_IBI_STS(reg) != IBI_STS_ACCEPTED) {
+ reg = readl(master->regs + SLV_EVENT_CTRL);
+ if ((reg & SLV_EVENT_CTRL_SIR_EN) == 0)
+ pr_warn("sir is disabled by master\n");
+ return -EACCES;
+ }
+
+ return 0;
+}
+
static int dw_i3c_master_reattach_i3c_dev(struct i3c_dev_desc *dev,
u8 old_dyn_addr)
{
@@ -1533,15 +1720,15 @@ static int dw_i3c_master_request_ibi(struct i3c_dev_desc *dev,
if (IS_ERR(data->ibi_pool))
return PTR_ERR(data->ibi_pool);
- spin_lock_irq(&master->ibi.lock);
+ spin_lock_irq(&master->ibi.master.lock);
for (i = 0; i < master->maxdevs; i++) {
- if (!master->ibi.slots[i]) {
+ if (!master->ibi.master.slots[i]) {
data->ibi = i;
- master->ibi.slots[i] = dev;
+ master->ibi.master.slots[i] = dev;
break;
}
}
- spin_unlock_irq(&master->ibi.lock);
+ spin_unlock_irq(&master->ibi.master.lock);
if (i >= master->maxdevs) {
i3c_generic_ibi_free_pool(data->ibi_pool);
@@ -1567,7 +1754,7 @@ static int dw_i3c_master_enable_ibi(struct i3c_dev_desc *dev)
* 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);
+ spin_lock_irq(&master->ibi.master.lock);
reg = readl(master->regs + IBI_SIR_REQ_REJECT) & ~BIT(dev->info.dyn_addr);
writel(reg, master->regs + IBI_SIR_REQ_REJECT);
@@ -1582,12 +1769,12 @@ static int dw_i3c_master_enable_ibi(struct i3c_dev_desc *dev)
reg |= DEV_ADDR_TABLE_IBI_WITH_DATA;
writel(reg, master->regs + dat_loc);
- spin_unlock_irq(&master->ibi.lock);
+ spin_unlock_irq(&master->ibi.master.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);
+ spin_lock_irq(&master->ibi.master.lock);
reg = readl(master->regs + IBI_SIR_REQ_REJECT);
reg |= BIT(dev->info.dyn_addr);
writel(reg, master->regs + IBI_SIR_REQ_REJECT);
@@ -1596,7 +1783,7 @@ static int dw_i3c_master_enable_ibi(struct i3c_dev_desc *dev)
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);
+ spin_unlock_irq(&master->ibi.master.lock);
}
return ret;
@@ -1621,7 +1808,7 @@ static int dw_i3c_master_disable_ibi(struct i3c_dev_desc *dev)
return pos;
}
- spin_lock_irq(&master->ibi.lock);
+ spin_lock_irq(&master->ibi.master.lock);
reg = readl(master->regs + IBI_SIR_REQ_REJECT);
reg |= BIT(dev->info.dyn_addr);
writel(reg, master->regs + IBI_SIR_REQ_REJECT);
@@ -1631,7 +1818,7 @@ static int dw_i3c_master_disable_ibi(struct i3c_dev_desc *dev)
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);
+ spin_unlock_irq(&master->ibi.master.lock);
return 0;
}
@@ -1642,10 +1829,10 @@ static void dw_i3c_master_free_ibi(struct i3c_dev_desc *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;
+ spin_lock_irq(&master->ibi.master.lock);
+ master->ibi.master.slots[data->ibi] = NULL;
data->ibi = -1;
- spin_unlock_irq(&master->ibi.lock);
+ spin_unlock_irq(&master->ibi.master.lock);
i3c_generic_ibi_free_pool(data->ibi_pool);
}
@@ -1708,8 +1895,9 @@ static struct i3c_dev_desc *dw_get_i3c_dev_by_addr(struct dw_i3c_master *master,
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];
+ if (master->ibi.master.slots[i] &&
+ master->ibi.master.slots[i]->info.dyn_addr == addr)
+ return master->ibi.master.slots[i];
}
return NULL;
}
@@ -1759,7 +1947,7 @@ static void dw_i3c_master_demux_ibis(struct dw_i3c_master *master)
nibi = QUEUE_STATUS_IBI_STATUS_CNT(readl(master->regs + QUEUE_STATUS_LEVEL));
- spin_lock(&master->ibi.lock);
+ spin_lock(&master->ibi.master.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);
@@ -1781,7 +1969,30 @@ static void dw_i3c_master_demux_ibis(struct dw_i3c_master *master)
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);
+ spin_unlock(&master->ibi.master.lock);
+}
+
+static void dw_i3c_target_handle_response_ready(struct dw_i3c_master *master)
+{
+ struct i3c_dev_desc *desc = master->base.this;
+ u32 reg = readl(master->regs + QUEUE_STATUS_LEVEL);
+ u32 nresp = QUEUE_STATUS_LEVEL_RESP(reg);
+ int i;
+
+ for (i = 0; i < nresp; i++) {
+ u32 resp = readl(master->regs + RESPONSE_QUEUE_PORT);
+ u32 nbytes = RESPONSE_PORT_DATA_LEN(resp);
+
+ if (nbytes > master->target_rx.max_len) {
+ dev_warn(master->dev, "private write data length is larger than max\n");
+ return;
+ }
+
+ dw_i3c_master_read_rx_fifo(master, master->target_rx.buf, nbytes);
+
+ if (desc->target_info.read_handler)
+ desc->target_info.read_handler(desc->dev, master->target_rx.buf, nbytes);
+ }
}
static irqreturn_t dw_i3c_master_irq_handler(int irq, void *dev_id)
@@ -1790,12 +2001,31 @@ static irqreturn_t dw_i3c_master_irq_handler(int irq, void *dev_id)
u32 status;
status = readl(master->regs + INTR_STATUS);
-
if (!(status & readl(master->regs + INTR_STATUS_EN))) {
writel(INTR_ALL, master->regs + INTR_STATUS);
return IRQ_NONE;
}
+ if (master->base.target) {
+ if (status & INTR_IBI_UPDATED_STAT) {
+ writel(INTR_IBI_UPDATED_STAT, master->regs + INTR_STATUS);
+ complete(&master->ibi.target.comp);
+ }
+
+ if (status & INTR_READ_REQ_RECV_STAT) {
+ /*
+ * TODO: Pass this information to the driver to take
+ * appropriate action.
+ */
+ dev_dbg(master->dev,
+ "private read received from controller when cmd queue is empty\n");
+ writel(INTR_READ_REQ_RECV_STAT, master->regs + INTR_STATUS);
+ }
+
+ if (status & INTR_RESP_READY_STAT)
+ dw_i3c_target_handle_response_ready(master);
+ }
+
spin_lock(&master->xferqueue.lock);
dw_i3c_master_end_xfer_locked(master, status);
if (status & INTR_TRANSFER_ERR_STAT)
@@ -1850,6 +2080,13 @@ static void dw_i3c_master_of_timings(struct dw_i3c_master *master,
master->timings.sda_tx_hold = val;
}
+static const struct i3c_target_ops dw_mipi_i3c_target_ops = {
+ .bus_init = dw_i3c_target_bus_init,
+ .bus_cleanup = dw_i3c_target_bus_cleanup,
+ .priv_xfers = dw_i3c_target_priv_xfers,
+ .generate_ibi = dw_i3c_target_generate_ibi,
+};
+
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,
@@ -1903,7 +2140,7 @@ 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);
+ spin_lock_init(&master->ibi.master.lock);
platform_set_drvdata(pdev, master);
@@ -1928,7 +2165,6 @@ static int dw_i3c_probe(struct platform_device *pdev)
master->maxdevs - 1));
}
#endif
-
writel(INTR_ALL, master->regs + INTR_STATUS);
irq = platform_get_irq(pdev, 0);
ret = devm_request_irq(&pdev->dev, irq,
@@ -1942,8 +2178,8 @@ static int dw_i3c_probe(struct platform_device *pdev)
dw_i3c_master_of_timings(master, pdev->dev.of_node);
- ret = i3c_master_register(&master->base, &pdev->dev,
- &dw_mipi_i3c_ops, false);
+ ret = i3c_register(&master->base, &pdev->dev, &dw_mipi_i3c_ops, &dw_mipi_i3c_target_ops,
+ false);
if (ret)
goto err_assert_rst;
@@ -1966,7 +2202,7 @@ static int dw_i3c_remove(struct platform_device *pdev)
struct dw_i3c_master *master = platform_get_drvdata(pdev);
int ret;
- ret = i3c_master_unregister(&master->base);
+ ret = i3c_unregister(&master->base);
if (ret)
return ret;