| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * I3C Controller driver |
| * Copyright 2025 Analog Devices Inc. |
| * Author: Jorge Marques <jorge.marques@analog.com> |
| */ |
| |
| #include <linux/bitops.h> |
| #include <linux/bitfield.h> |
| #include <linux/clk.h> |
| #include <linux/err.h> |
| #include <linux/errno.h> |
| #include <linux/adi-axi-common.h> |
| #include <linux/i3c/master.h> |
| #include <linux/interrupt.h> |
| #include <linux/io.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/platform_device.h> |
| |
| #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 <jorge.marques@analog.com>"); |
| MODULE_DESCRIPTION("Analog Devices I3C master driver"); |
| MODULE_LICENSE("GPL"); |