root/drivers/i3c/master/adi-i3c-master.c
// 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_flex(*xfer, cmds, ncmds);
        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_cmd *ccmd;

        struct adi_i3c_xfer *xfer __free(kfree) = 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_i3c_xfers(struct i3c_dev_desc *dev,
                                    struct i3c_xfer *xfers,
                                    int nxfers, enum i3c_xfer_mode mode)
{
        struct i3c_master_controller *m = i3c_dev_get_master(dev);
        struct adi_i3c_master *master = to_adi_i3c_master(m);
        int i, ret;

        if (!nxfers)
                return 0;

        struct adi_i3c_xfer *xfer __free(kfree) = 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_obj(*data);
        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_obj(*data);
        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);
        int i;

        if (!nxfers)
                return 0;
        for (i = 0; i < nxfers; i++) {
                if (xfers[i].flags & I2C_M_TEN)
                        return -EOPNOTSUPP;
        }

        struct adi_i3c_xfer *xfer __free(kfree) = 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,
        .i3c_xfers = adi_i3c_master_i3c_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");