diff options
author | Iwona Winiarska <iwona.winiarska@intel.com> | 2022-02-20 01:39:51 +0300 |
---|---|---|
committer | Iwona Winiarska <iwona.winiarska@intel.com> | 2022-04-12 14:07:34 +0300 |
commit | cabd83ebe9d015c60012f5e78c340ac2b001ec98 (patch) | |
tree | 2d01202b996fbbe194cfa24ec86397e63016e7fc /drivers/i3c | |
parent | bcc82f5f230e4ad36c928f0d2caf93eeb58aa64c (diff) | |
download | linux-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.c | 306 |
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; |