// SPDX-License-Identifier: GPL-2.0-only /* * I3C Controller driver * Copyright 2025 Analog Devices Inc. * Author: Jorge Marques */ #include #include #include #include #include #include #include #include #include #include #include #include #include "../internals.h" #define ADI_MAX_DEVS 16 #define ADI_HAS_MDB_FROM_BCR(x) (FIELD_GET(BIT(2), (x))) #define REG_ENABLE 0x040 #define REG_PID_L 0x054 #define REG_PID_H 0x058 #define REG_DCR_BCR_DA 0x05c #define REG_DCR_BCR_DA_GET_DA(x) FIELD_GET(GENMASK(22, 16), (x)) #define REG_DCR_BCR_DA_GET_BCR(x) FIELD_GET(GENMASK(15, 8), (x)) #define REG_DCR_BCR_DA_GET_DCR(x) FIELD_GET(GENMASK(7, 0), (x)) #define REG_IRQ_MASK 0x080 #define REG_IRQ_PENDING 0x084 #define REG_IRQ_PENDING_DAA BIT(7) #define REG_IRQ_PENDING_IBI BIT(6) #define REG_IRQ_PENDING_CMDR BIT(5) #define REG_CMD_FIFO 0x0d4 #define REG_CMD_FIFO_0_IS_CCC BIT(22) #define REG_CMD_FIFO_0_BCAST BIT(21) #define REG_CMD_FIFO_0_SR BIT(20) #define REG_CMD_FIFO_0_LEN(l) FIELD_PREP(GENMASK(19, 8), (l)) #define REG_CMD_FIFO_0_DEV_ADDR(a) FIELD_PREP(GENMASK(7, 1), (a)) #define REG_CMD_FIFO_0_RNW BIT(0) #define REG_CMD_FIFO_1_CCC(id) FIELD_PREP(GENMASK(7, 0), (id)) #define REG_CMD_FIFO_ROOM 0x0c0 #define REG_CMDR_FIFO 0x0d8 #define REG_CMDR_FIFO_UDA_ERROR 8 #define REG_CMDR_FIFO_NACK_RESP 6 #define REG_CMDR_FIFO_CE2_ERROR 4 #define REG_CMDR_FIFO_CE0_ERROR 1 #define REG_CMDR_FIFO_NO_ERROR 0 #define REG_CMDR_FIFO_ERROR(x) FIELD_GET(GENMASK(23, 20), (x)) #define REG_CMDR_FIFO_XFER_BYTES(x) FIELD_GET(GENMASK(19, 8), (x)) #define REG_SDO_FIFO 0x0dc #define REG_SDO_FIFO_ROOM 0x0c8 #define REG_SDI_FIFO 0x0e0 #define REG_IBI_FIFO 0x0e4 #define REG_FIFO_STATUS 0x0e8 #define REG_FIFO_STATUS_CMDR_EMPTY BIT(0) #define REG_FIFO_STATUS_IBI_EMPTY BIT(1) #define REG_OPS 0x100 #define REG_OPS_PP_SG_MASK GENMASK(6, 5) #define REG_OPS_SET_SG(x) FIELD_PREP(REG_OPS_PP_SG_MASK, (x)) #define REG_IBI_CONFIG 0x140 #define REG_IBI_CONFIG_ENABLE BIT(0) #define REG_IBI_CONFIG_LISTEN BIT(1) #define REG_DEV_CHAR 0x180 #define REG_DEV_CHAR_IS_I2C BIT(0) #define REG_DEV_CHAR_IS_ATTACHED BIT(1) #define REG_DEV_CHAR_BCR_IBI(x) FIELD_PREP(GENMASK(3, 2), (x)) #define REG_DEV_CHAR_WEN BIT(8) #define REG_DEV_CHAR_ADDR(x) FIELD_PREP(GENMASK(15, 9), (x)) enum speed_grade {PP_SG_UNSET, PP_SG_1MHZ, PP_SG_3MHZ, PP_SG_6MHZ, PP_SG_12MHZ}; struct adi_i3c_cmd { u32 cmd0; u32 cmd1; u32 tx_len; const void *tx_buf; u32 rx_len; void *rx_buf; u32 error; }; struct adi_i3c_xfer { struct list_head node; struct completion comp; int ret; unsigned int ncmds; unsigned int ncmds_comp; struct adi_i3c_cmd cmds[] __counted_by(ncmds); }; struct adi_i3c_master { struct i3c_master_controller base; u32 free_rr_slots; struct { unsigned int num_slots; struct i3c_dev_desc **slots; spinlock_t lock; /* Protect IBI slot access */ } ibi; struct { struct list_head list; struct adi_i3c_xfer *cur; spinlock_t lock; /* Protect transfer */ } xferqueue; void __iomem *regs; struct clk *clk; unsigned long i3c_scl_lim; struct { u8 addrs[ADI_MAX_DEVS]; u8 index; } daa; }; static inline struct adi_i3c_master *to_adi_i3c_master(struct i3c_master_controller *master) { return container_of(master, struct adi_i3c_master, base); } static void adi_i3c_master_wr_to_tx_fifo(struct adi_i3c_master *master, const u8 *buf, unsigned int nbytes) { unsigned int n, m; n = readl(master->regs + REG_SDO_FIFO_ROOM); m = min(n, nbytes); i3c_writel_fifo(master->regs + REG_SDO_FIFO, buf, m); } static void adi_i3c_master_rd_from_rx_fifo(struct adi_i3c_master *master, u8 *buf, unsigned int nbytes) { i3c_readl_fifo(master->regs + REG_SDI_FIFO, buf, nbytes); } static bool adi_i3c_master_supports_ccc_cmd(struct i3c_master_controller *m, const struct i3c_ccc_cmd *cmd) { if (cmd->ndests > 1) return false; switch (cmd->id) { case I3C_CCC_ENEC(true): case I3C_CCC_ENEC(false): case I3C_CCC_DISEC(true): case I3C_CCC_DISEC(false): case I3C_CCC_RSTDAA(true): case I3C_CCC_RSTDAA(false): case I3C_CCC_ENTDAA: case I3C_CCC_SETDASA: case I3C_CCC_SETNEWDA: case I3C_CCC_GETMWL: case I3C_CCC_GETMRL: case I3C_CCC_GETPID: case I3C_CCC_GETBCR: case I3C_CCC_GETDCR: case I3C_CCC_GETSTATUS: case I3C_CCC_GETHDRCAP: return true; default: break; } return false; } static int adi_i3c_master_disable(struct adi_i3c_master *master) { writel(0, master->regs + REG_IBI_CONFIG); return 0; } static struct adi_i3c_xfer *adi_i3c_master_alloc_xfer(struct adi_i3c_master *master, unsigned int ncmds) { struct adi_i3c_xfer *xfer; xfer = kzalloc(struct_size(xfer, cmds, ncmds), GFP_KERNEL); if (!xfer) return NULL; INIT_LIST_HEAD(&xfer->node); xfer->ncmds = ncmds; xfer->ret = -ETIMEDOUT; return xfer; } static void adi_i3c_master_start_xfer_locked(struct adi_i3c_master *master) { struct adi_i3c_xfer *xfer = master->xferqueue.cur; unsigned int i, n, m; if (!xfer) return; for (i = 0; i < xfer->ncmds; i++) { struct adi_i3c_cmd *cmd = &xfer->cmds[i]; if (!(cmd->cmd0 & REG_CMD_FIFO_0_RNW)) adi_i3c_master_wr_to_tx_fifo(master, cmd->tx_buf, cmd->tx_len); } n = readl(master->regs + REG_CMD_FIFO_ROOM); for (i = 0; i < xfer->ncmds; i++) { struct adi_i3c_cmd *cmd = &xfer->cmds[i]; m = cmd->cmd0 & REG_CMD_FIFO_0_IS_CCC ? 2 : 1; if (m > n) break; writel(cmd->cmd0, master->regs + REG_CMD_FIFO); if (cmd->cmd0 & REG_CMD_FIFO_0_IS_CCC) writel(cmd->cmd1, master->regs + REG_CMD_FIFO); n -= m; } } static void adi_i3c_master_end_xfer_locked(struct adi_i3c_master *master, u32 pending) { struct adi_i3c_xfer *xfer = master->xferqueue.cur; int i, ret = 0; if (!xfer) return; while (!(readl(master->regs + REG_FIFO_STATUS) & REG_FIFO_STATUS_CMDR_EMPTY)) { struct adi_i3c_cmd *cmd; u32 cmdr, rx_len; cmdr = readl(master->regs + REG_CMDR_FIFO); cmd = &xfer->cmds[xfer->ncmds_comp++]; if (cmd->cmd0 & REG_CMD_FIFO_0_RNW) { rx_len = min_t(u32, REG_CMDR_FIFO_XFER_BYTES(cmdr), cmd->rx_len); adi_i3c_master_rd_from_rx_fifo(master, cmd->rx_buf, rx_len); } cmd->error = REG_CMDR_FIFO_ERROR(cmdr); } for (i = 0; i < xfer->ncmds_comp; i++) { switch (xfer->cmds[i].error) { case REG_CMDR_FIFO_NO_ERROR: break; case REG_CMDR_FIFO_CE0_ERROR: case REG_CMDR_FIFO_CE2_ERROR: case REG_CMDR_FIFO_NACK_RESP: case REG_CMDR_FIFO_UDA_ERROR: ret = -EIO; break; default: ret = -EINVAL; break; } } xfer->ret = ret; if (xfer->ncmds_comp != xfer->ncmds) return; complete(&xfer->comp); xfer = list_first_entry_or_null(&master->xferqueue.list, struct adi_i3c_xfer, node); if (xfer) list_del_init(&xfer->node); master->xferqueue.cur = xfer; adi_i3c_master_start_xfer_locked(master); } static void adi_i3c_master_queue_xfer(struct adi_i3c_master *master, struct adi_i3c_xfer *xfer) { init_completion(&xfer->comp); guard(spinlock_irqsave)(&master->xferqueue.lock); if (master->xferqueue.cur) { list_add_tail(&xfer->node, &master->xferqueue.list); } else { master->xferqueue.cur = xfer; adi_i3c_master_start_xfer_locked(master); } } static void adi_i3c_master_unqueue_xfer(struct adi_i3c_master *master, struct adi_i3c_xfer *xfer) { guard(spinlock_irqsave)(&master->xferqueue.lock); if (master->xferqueue.cur == xfer) master->xferqueue.cur = NULL; else list_del_init(&xfer->node); writel(0x01, master->regs + REG_ENABLE); writel(0x00, master->regs + REG_ENABLE); writel(REG_IRQ_PENDING_CMDR, master->regs + REG_IRQ_MASK); } static enum i3c_error_code adi_i3c_cmd_get_err(struct adi_i3c_cmd *cmd) { switch (cmd->error) { case REG_CMDR_FIFO_CE0_ERROR: return I3C_ERROR_M0; case REG_CMDR_FIFO_CE2_ERROR: case REG_CMDR_FIFO_NACK_RESP: return I3C_ERROR_M2; default: break; } return I3C_ERROR_UNKNOWN; } static int adi_i3c_master_send_ccc_cmd(struct i3c_master_controller *m, struct i3c_ccc_cmd *cmd) { struct adi_i3c_master *master = to_adi_i3c_master(m); struct adi_i3c_xfer *xfer __free(kfree) = NULL; struct adi_i3c_cmd *ccmd; xfer = adi_i3c_master_alloc_xfer(master, 1); if (!xfer) return -ENOMEM; ccmd = xfer->cmds; ccmd->cmd1 = REG_CMD_FIFO_1_CCC(cmd->id); ccmd->cmd0 = REG_CMD_FIFO_0_IS_CCC | REG_CMD_FIFO_0_LEN(cmd->dests[0].payload.len); if (cmd->id & I3C_CCC_DIRECT) ccmd->cmd0 |= REG_CMD_FIFO_0_DEV_ADDR(cmd->dests[0].addr); if (cmd->rnw) { ccmd->cmd0 |= REG_CMD_FIFO_0_RNW; ccmd->rx_buf = cmd->dests[0].payload.data; ccmd->rx_len = cmd->dests[0].payload.len; } else { ccmd->tx_buf = cmd->dests[0].payload.data; ccmd->tx_len = cmd->dests[0].payload.len; } adi_i3c_master_queue_xfer(master, xfer); if (!wait_for_completion_timeout(&xfer->comp, msecs_to_jiffies(1000))) adi_i3c_master_unqueue_xfer(master, xfer); cmd->err = adi_i3c_cmd_get_err(&xfer->cmds[0]); return 0; } static int adi_i3c_master_priv_xfers(struct i3c_dev_desc *dev, struct i3c_priv_xfer *xfers, int nxfers) { struct i3c_master_controller *m = i3c_dev_get_master(dev); struct adi_i3c_master *master = to_adi_i3c_master(m); struct adi_i3c_xfer *xfer __free(kfree) = NULL; int i, ret; if (!nxfers) return 0; xfer = adi_i3c_master_alloc_xfer(master, nxfers); if (!xfer) return -ENOMEM; for (i = 0; i < nxfers; i++) { struct adi_i3c_cmd *ccmd = &xfer->cmds[i]; ccmd->cmd0 = REG_CMD_FIFO_0_DEV_ADDR(dev->info.dyn_addr); if (xfers[i].rnw) { ccmd->cmd0 |= REG_CMD_FIFO_0_RNW; ccmd->rx_buf = xfers[i].data.in; ccmd->rx_len = xfers[i].len; } else { ccmd->tx_buf = xfers[i].data.out; ccmd->tx_len = xfers[i].len; } ccmd->cmd0 |= REG_CMD_FIFO_0_LEN(xfers[i].len); if (i < nxfers - 1) ccmd->cmd0 |= REG_CMD_FIFO_0_SR; if (!i) ccmd->cmd0 |= REG_CMD_FIFO_0_BCAST; } adi_i3c_master_queue_xfer(master, xfer); if (!wait_for_completion_timeout(&xfer->comp, msecs_to_jiffies(1000))) adi_i3c_master_unqueue_xfer(master, xfer); ret = xfer->ret; for (i = 0; i < nxfers; i++) xfers[i].err = adi_i3c_cmd_get_err(&xfer->cmds[i]); return ret; } struct adi_i3c_i2c_dev_data { struct i3c_generic_ibi_pool *ibi_pool; u16 id; s16 ibi; }; static int adi_i3c_master_get_rr_slot(struct adi_i3c_master *master, u8 dyn_addr) { if (!master->free_rr_slots) return -ENOSPC; return ffs(master->free_rr_slots) - 1; } static int adi_i3c_master_reattach_i3c_dev(struct i3c_dev_desc *dev, u8 dyn_addr) { struct i3c_master_controller *m = i3c_dev_get_master(dev); struct adi_i3c_master *master = to_adi_i3c_master(m); u8 addr; addr = dev->info.dyn_addr ? dev->info.dyn_addr : dev->info.static_addr; writel(REG_DEV_CHAR_ADDR(dyn_addr), master->regs + REG_DEV_CHAR); writel((readl(master->regs + REG_DEV_CHAR) & ~REG_DEV_CHAR_IS_ATTACHED) | REG_DEV_CHAR_WEN, master->regs + REG_DEV_CHAR); writel(REG_DEV_CHAR_ADDR(addr), master->regs + REG_DEV_CHAR); writel(readl(master->regs + REG_DEV_CHAR) | REG_DEV_CHAR_IS_ATTACHED | REG_DEV_CHAR_WEN, master->regs + REG_DEV_CHAR); return 0; } static int adi_i3c_master_attach_i3c_dev(struct i3c_dev_desc *dev) { struct i3c_master_controller *m = i3c_dev_get_master(dev); struct adi_i3c_master *master = to_adi_i3c_master(m); struct adi_i3c_i2c_dev_data *data; int slot; u8 addr; data = kzalloc(sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; slot = adi_i3c_master_get_rr_slot(master, dev->info.dyn_addr); if (slot < 0) { kfree(data); return slot; } data->id = slot; i3c_dev_set_master_data(dev, data); master->free_rr_slots &= ~BIT(slot); addr = dev->info.dyn_addr ? dev->info.dyn_addr : dev->info.static_addr; writel(REG_DEV_CHAR_ADDR(addr), master->regs + REG_DEV_CHAR); writel(readl(master->regs + REG_DEV_CHAR) | REG_DEV_CHAR_IS_ATTACHED | REG_DEV_CHAR_WEN, master->regs + REG_DEV_CHAR); return 0; } static void adi_i3c_master_sync_dev_char(struct i3c_master_controller *m) { struct adi_i3c_master *master = to_adi_i3c_master(m); struct i3c_dev_desc *i3cdev; u32 bcr_ibi; u8 addr; i3c_bus_for_each_i3cdev(&m->bus, i3cdev) { addr = i3cdev->info.dyn_addr ? i3cdev->info.dyn_addr : i3cdev->info.static_addr; writel(REG_DEV_CHAR_ADDR(addr), master->regs + REG_DEV_CHAR); bcr_ibi = FIELD_GET(I3C_BCR_IBI_PAYLOAD | I3C_BCR_IBI_REQ_CAP, (i3cdev->info.bcr)); writel(readl(master->regs + REG_DEV_CHAR) | REG_DEV_CHAR_BCR_IBI(bcr_ibi) | REG_DEV_CHAR_WEN, master->regs + REG_DEV_CHAR); } } static void adi_i3c_master_detach_i3c_dev(struct i3c_dev_desc *dev) { struct i3c_master_controller *m = i3c_dev_get_master(dev); struct adi_i3c_master *master = to_adi_i3c_master(m); struct adi_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev); u8 addr; addr = dev->info.dyn_addr ? dev->info.dyn_addr : dev->info.static_addr; writel(REG_DEV_CHAR_ADDR(addr), master->regs + REG_DEV_CHAR); writel((readl(master->regs + REG_DEV_CHAR) & ~REG_DEV_CHAR_IS_ATTACHED) | REG_DEV_CHAR_WEN, master->regs + REG_DEV_CHAR); i3c_dev_set_master_data(dev, NULL); master->free_rr_slots |= BIT(data->id); kfree(data); } static int adi_i3c_master_attach_i2c_dev(struct i2c_dev_desc *dev) { struct i3c_master_controller *m = i2c_dev_get_master(dev); struct adi_i3c_master *master = to_adi_i3c_master(m); struct adi_i3c_i2c_dev_data *data; int slot; slot = adi_i3c_master_get_rr_slot(master, 0); if (slot < 0) return slot; data = kzalloc(sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; data->id = slot; master->free_rr_slots &= ~BIT(slot); i2c_dev_set_master_data(dev, data); writel(REG_DEV_CHAR_ADDR(dev->addr) | REG_DEV_CHAR_IS_I2C | REG_DEV_CHAR_IS_ATTACHED | REG_DEV_CHAR_WEN, master->regs + REG_DEV_CHAR); return 0; } static void adi_i3c_master_detach_i2c_dev(struct i2c_dev_desc *dev) { struct i3c_master_controller *m = i2c_dev_get_master(dev); struct adi_i3c_master *master = to_adi_i3c_master(m); struct adi_i3c_i2c_dev_data *data = i2c_dev_get_master_data(dev); writel(REG_DEV_CHAR_ADDR(dev->addr) | REG_DEV_CHAR_IS_I2C | REG_DEV_CHAR_WEN, master->regs + REG_DEV_CHAR); i2c_dev_set_master_data(dev, NULL); master->free_rr_slots |= BIT(data->id); kfree(data); } static void adi_i3c_master_bus_cleanup(struct i3c_master_controller *m) { struct adi_i3c_master *master = to_adi_i3c_master(m); adi_i3c_master_disable(master); } static void adi_i3c_master_upd_i3c_scl_lim(struct adi_i3c_master *master) { struct i3c_master_controller *m = &master->base; struct i3c_bus *bus = i3c_master_get_bus(m); u8 i3c_scl_lim = 0; struct i3c_dev_desc *dev; u8 pp_sg; i3c_bus_for_each_i3cdev(bus, dev) { u8 max_fscl; max_fscl = max(I3C_CCC_MAX_SDR_FSCL(dev->info.max_read_ds), I3C_CCC_MAX_SDR_FSCL(dev->info.max_write_ds)); switch (max_fscl) { case I3C_SDR1_FSCL_8MHZ: max_fscl = PP_SG_6MHZ; break; case I3C_SDR2_FSCL_6MHZ: max_fscl = PP_SG_3MHZ; break; case I3C_SDR3_FSCL_4MHZ: max_fscl = PP_SG_3MHZ; break; case I3C_SDR4_FSCL_2MHZ: max_fscl = PP_SG_1MHZ; break; case I3C_SDR0_FSCL_MAX: default: max_fscl = PP_SG_12MHZ; break; } if (max_fscl && (i3c_scl_lim > max_fscl || !i3c_scl_lim)) i3c_scl_lim = max_fscl; } if (!i3c_scl_lim) return; master->i3c_scl_lim = i3c_scl_lim - 1; pp_sg = readl(master->regs + REG_OPS) & ~REG_OPS_PP_SG_MASK; pp_sg |= REG_OPS_SET_SG(master->i3c_scl_lim); writel(pp_sg, master->regs + REG_OPS); } static void adi_i3c_master_get_features(struct adi_i3c_master *master, unsigned int slot, struct i3c_device_info *info) { u32 buf; /* Dynamic address and PID are for identification only */ memset(info, 0, sizeof(*info)); buf = readl(master->regs + REG_DCR_BCR_DA); info->dyn_addr = REG_DCR_BCR_DA_GET_DA(buf); info->dcr = REG_DCR_BCR_DA_GET_DCR(buf); info->bcr = REG_DCR_BCR_DA_GET_BCR(buf); info->pid = readl(master->regs + REG_PID_L); info->pid |= (u64)readl(master->regs + REG_PID_H) << 32; } static int adi_i3c_master_do_daa(struct i3c_master_controller *m) { struct adi_i3c_master *master = to_adi_i3c_master(m); int ret, addr = 0; u32 irq_mask; for (u8 i = 0; i < ADI_MAX_DEVS; i++) { addr = i3c_master_get_free_addr(m, addr); if (addr < 0) return addr; master->daa.addrs[i] = addr; } irq_mask = readl(master->regs + REG_IRQ_MASK); writel(irq_mask | REG_IRQ_PENDING_DAA, master->regs + REG_IRQ_MASK); master->daa.index = 0; ret = i3c_master_entdaa_locked(&master->base); writel(irq_mask, master->regs + REG_IRQ_MASK); /* DAA always finishes with CE2_ERROR or NACK_RESP */ if (ret && ret != I3C_ERROR_M2) return ret; /* Add I3C devices discovered */ for (u8 i = 0; i < master->daa.index; i++) i3c_master_add_i3c_dev_locked(m, master->daa.addrs[i]); /* Sync retrieved devs info with the IP */ adi_i3c_master_sync_dev_char(m); i3c_master_defslvs_locked(&master->base); adi_i3c_master_upd_i3c_scl_lim(master); return 0; } static int adi_i3c_master_bus_init(struct i3c_master_controller *m) { struct adi_i3c_master *master = to_adi_i3c_master(m); struct i3c_device_info info = { }; int ret; ret = i3c_master_get_free_addr(m, 0); if (ret < 0) return ret; adi_i3c_master_get_features(master, 0, &info); ret = i3c_master_set_info(&master->base, &info); if (ret) return ret; writel(REG_IBI_CONFIG_LISTEN, master->regs + REG_IBI_CONFIG); return 0; } static void adi_i3c_master_handle_ibi(struct adi_i3c_master *master, u32 raw) { struct adi_i3c_i2c_dev_data *data; struct i3c_ibi_slot *slot; struct i3c_dev_desc *dev; u8 da, id, mdb, len; u8 *buf; da = FIELD_GET(GENMASK(23, 17), raw); mdb = FIELD_GET(GENMASK(15, 8), raw); for (id = 0; id < master->ibi.num_slots; id++) { if (master->ibi.slots[id] && master->ibi.slots[id]->info.dyn_addr == da) break; } if (id == master->ibi.num_slots) return; dev = master->ibi.slots[id]; len = ADI_HAS_MDB_FROM_BCR(dev->info.bcr); data = i3c_dev_get_master_data(dev); guard(spinlock)(&master->ibi.lock); slot = i3c_generic_ibi_get_free_slot(data->ibi_pool); if (!slot) return; slot->len = len; buf = slot->data; buf[0] = mdb; i3c_master_queue_ibi(dev, slot); } static void adi_i3c_master_demux_ibis(struct adi_i3c_master *master) { while (!(readl(master->regs + REG_FIFO_STATUS) & REG_FIFO_STATUS_IBI_EMPTY)) { u32 raw = readl(master->regs + REG_IBI_FIFO); adi_i3c_master_handle_ibi(master, raw); } } static void adi_i3c_master_handle_da_req(struct adi_i3c_master *master) { u8 payload0[8]; u32 addr; adi_i3c_master_rd_from_rx_fifo(master, payload0, 6); addr = master->daa.addrs[master->daa.index++]; addr = (addr << 1) | (parity8(addr) ? 0 : 1); writel(addr, master->regs + REG_SDO_FIFO); } static irqreturn_t adi_i3c_master_irq(int irq, void *data) { struct adi_i3c_master *master = data; u32 pending; pending = readl(master->regs + REG_IRQ_PENDING); writel(pending, master->regs + REG_IRQ_PENDING); if (pending & REG_IRQ_PENDING_CMDR) { scoped_guard(spinlock_irqsave, &master->xferqueue.lock) { adi_i3c_master_end_xfer_locked(master, pending); } } if (pending & REG_IRQ_PENDING_IBI) adi_i3c_master_demux_ibis(master); if (pending & REG_IRQ_PENDING_DAA) adi_i3c_master_handle_da_req(master); return IRQ_HANDLED; } static int adi_i3c_master_i2c_xfers(struct i2c_dev_desc *dev, struct i2c_msg *xfers, int nxfers) { struct i3c_master_controller *m = i2c_dev_get_master(dev); struct adi_i3c_master *master = to_adi_i3c_master(m); struct adi_i3c_xfer *xfer __free(kfree) = NULL; int i; if (!nxfers) return 0; for (i = 0; i < nxfers; i++) { if (xfers[i].flags & I2C_M_TEN) return -EOPNOTSUPP; } xfer = adi_i3c_master_alloc_xfer(master, nxfers); if (!xfer) return -ENOMEM; for (i = 0; i < nxfers; i++) { struct adi_i3c_cmd *ccmd = &xfer->cmds[i]; ccmd->cmd0 = REG_CMD_FIFO_0_DEV_ADDR(xfers[i].addr); if (xfers[i].flags & I2C_M_RD) { ccmd->cmd0 |= REG_CMD_FIFO_0_RNW; ccmd->rx_buf = xfers[i].buf; ccmd->rx_len = xfers[i].len; } else { ccmd->tx_buf = xfers[i].buf; ccmd->tx_len = xfers[i].len; } ccmd->cmd0 |= REG_CMD_FIFO_0_LEN(xfers[i].len); } adi_i3c_master_queue_xfer(master, xfer); if (!wait_for_completion_timeout(&xfer->comp, m->i2c.timeout)) adi_i3c_master_unqueue_xfer(master, xfer); return xfer->ret; } static int adi_i3c_master_disable_ibi(struct i3c_dev_desc *dev) { struct i3c_master_controller *m = i3c_dev_get_master(dev); struct adi_i3c_master *master = to_adi_i3c_master(m); struct i3c_dev_desc *i3cdev; u32 enabled = 0; int ret; ret = i3c_master_disec_locked(m, dev->info.dyn_addr, I3C_CCC_EVENT_SIR); i3c_bus_for_each_i3cdev(&m->bus, i3cdev) { if (dev != i3cdev && i3cdev->ibi) enabled |= i3cdev->ibi->enabled; } if (!enabled) { writel(REG_IBI_CONFIG_LISTEN, master->regs + REG_IBI_CONFIG); writel(readl(master->regs + REG_IRQ_MASK) & ~REG_IRQ_PENDING_IBI, master->regs + REG_IRQ_MASK); } return ret; } static int adi_i3c_master_enable_ibi(struct i3c_dev_desc *dev) { struct i3c_master_controller *m = i3c_dev_get_master(dev); struct adi_i3c_master *master = to_adi_i3c_master(m); writel(REG_IBI_CONFIG_LISTEN | REG_IBI_CONFIG_ENABLE, master->regs + REG_IBI_CONFIG); writel(readl(master->regs + REG_IRQ_MASK) | REG_IRQ_PENDING_IBI, master->regs + REG_IRQ_MASK); return i3c_master_enec_locked(m, dev->info.dyn_addr, I3C_CCC_EVENT_SIR); } static int adi_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 adi_i3c_master *master = to_adi_i3c_master(m); struct adi_i3c_i2c_dev_data *data; unsigned int i; data = i3c_dev_get_master_data(dev); data->ibi_pool = i3c_generic_ibi_alloc_pool(dev, req); if (IS_ERR(data->ibi_pool)) return PTR_ERR(data->ibi_pool); scoped_guard(spinlock_irqsave, &master->ibi.lock) { for (i = 0; i < master->ibi.num_slots; i++) { if (!master->ibi.slots[i]) { data->ibi = i; master->ibi.slots[i] = dev; break; } } } if (i < master->ibi.num_slots) return 0; i3c_generic_ibi_free_pool(data->ibi_pool); data->ibi_pool = NULL; return -ENOSPC; } static void adi_i3c_master_free_ibi(struct i3c_dev_desc *dev) { struct i3c_master_controller *m = i3c_dev_get_master(dev); struct adi_i3c_master *master = to_adi_i3c_master(m); struct adi_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev); scoped_guard(spinlock_irqsave, &master->ibi.lock) { master->ibi.slots[data->ibi] = NULL; } i3c_generic_ibi_free_pool(data->ibi_pool); } static void adi_i3c_master_recycle_ibi_slot(struct i3c_dev_desc *dev, struct i3c_ibi_slot *slot) { struct adi_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev); i3c_generic_ibi_recycle_slot(data->ibi_pool, slot); } static const struct i3c_master_controller_ops adi_i3c_master_ops = { .bus_init = adi_i3c_master_bus_init, .bus_cleanup = adi_i3c_master_bus_cleanup, .attach_i3c_dev = adi_i3c_master_attach_i3c_dev, .reattach_i3c_dev = adi_i3c_master_reattach_i3c_dev, .detach_i3c_dev = adi_i3c_master_detach_i3c_dev, .attach_i2c_dev = adi_i3c_master_attach_i2c_dev, .detach_i2c_dev = adi_i3c_master_detach_i2c_dev, .do_daa = adi_i3c_master_do_daa, .supports_ccc_cmd = adi_i3c_master_supports_ccc_cmd, .send_ccc_cmd = adi_i3c_master_send_ccc_cmd, .priv_xfers = adi_i3c_master_priv_xfers, .i2c_xfers = adi_i3c_master_i2c_xfers, .request_ibi = adi_i3c_master_request_ibi, .enable_ibi = adi_i3c_master_enable_ibi, .disable_ibi = adi_i3c_master_disable_ibi, .free_ibi = adi_i3c_master_free_ibi, .recycle_ibi_slot = adi_i3c_master_recycle_ibi_slot, }; static const struct of_device_id adi_i3c_master_of_match[] = { { .compatible = "adi,i3c-master-v1" }, {} }; static int adi_i3c_master_probe(struct platform_device *pdev) { struct adi_i3c_master *master; struct clk_bulk_data *clk; unsigned int version; int ret, irq; master = devm_kzalloc(&pdev->dev, sizeof(*master), GFP_KERNEL); if (!master) return -ENOMEM; master->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(master->regs)) return PTR_ERR(master->regs); ret = devm_clk_bulk_get_all_enabled(&pdev->dev, &clk); if (ret < 0) return dev_err_probe(&pdev->dev, ret, "Failed to get clocks\n"); irq = platform_get_irq(pdev, 0); if (irq < 0) return irq; version = readl(master->regs + ADI_AXI_REG_VERSION); if (ADI_AXI_PCORE_VER_MAJOR(version) != 1) dev_err_probe(&pdev->dev, -ENODEV, "Unsupported peripheral version %u.%u.%u\n", ADI_AXI_PCORE_VER_MAJOR(version), ADI_AXI_PCORE_VER_MINOR(version), ADI_AXI_PCORE_VER_PATCH(version)); writel(0x00, master->regs + REG_ENABLE); writel(0x00, master->regs + REG_IRQ_MASK); ret = devm_request_irq(&pdev->dev, irq, adi_i3c_master_irq, 0, dev_name(&pdev->dev), master); if (ret) return ret; platform_set_drvdata(pdev, master); master->free_rr_slots = GENMASK(ADI_MAX_DEVS, 1); writel(REG_IRQ_PENDING_CMDR, master->regs + REG_IRQ_MASK); spin_lock_init(&master->ibi.lock); master->ibi.num_slots = 15; master->ibi.slots = devm_kcalloc(&pdev->dev, master->ibi.num_slots, sizeof(*master->ibi.slots), GFP_KERNEL); if (!master->ibi.slots) return -ENOMEM; spin_lock_init(&master->xferqueue.lock); INIT_LIST_HEAD(&master->xferqueue.list); return i3c_master_register(&master->base, &pdev->dev, &adi_i3c_master_ops, false); } static void adi_i3c_master_remove(struct platform_device *pdev) { struct adi_i3c_master *master = platform_get_drvdata(pdev); writel(0xff, master->regs + REG_IRQ_PENDING); writel(0x00, master->regs + REG_IRQ_MASK); writel(0x01, master->regs + REG_ENABLE); i3c_master_unregister(&master->base); } static struct platform_driver adi_i3c_master = { .probe = adi_i3c_master_probe, .remove = adi_i3c_master_remove, .driver = { .name = "adi-i3c-master", .of_match_table = adi_i3c_master_of_match, }, }; module_platform_driver(adi_i3c_master); MODULE_AUTHOR("Jorge Marques "); MODULE_DESCRIPTION("Analog Devices I3C master driver"); MODULE_LICENSE("GPL");