diff options
author | Oleksandr Shulzhenko <oleksandr.shulzhenko.viktorovych@intel.com> | 2021-09-09 16:05:26 +0300 |
---|---|---|
committer | Oleksandr Shulzhenko <oleksandr.shulzhenko.viktorovych@intel.com> | 2021-11-18 13:02:55 +0300 |
commit | ee31c3564a2599abc9d3c30d964f0a429220fafc (patch) | |
tree | 7ad5939b462fd4fdc0f8418c7d7e6360f6220d76 /drivers/i3c | |
parent | c7981259e359523e74044f926b69a804c61c86b1 (diff) | |
download | linux-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.c | 294 |
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 */ |