Skip to content

Commit

Permalink
drivers: i3c: adi-i3c-master: Add IBI support
Browse files Browse the repository at this point in the history
Add the optional IBI methods for the I3C Controller.

Signed-off-by: Jorge Marques <jorge.marques@analog.com>
  • Loading branch information
gastmaier authored and cseci committed May 7, 2024
1 parent e6c9a02 commit a9e0da2
Showing 1 changed file with 205 additions and 5 deletions.
210 changes: 205 additions & 5 deletions drivers/i3c/master/adi-i3c-master.c
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,17 @@

#define DEV_CHAR_IS_I2C BIT(0)
#define DEV_CHAR_IS_ATTACHED BIT(1)
#define DEV_CHAR_IS_IBI_CAPABLE BIT(2)
#define DEV_CHAR_BCR_IBI(x) (((x) & GENMASK(2, 1)) << 1)
#define DEV_CHAR_HAS_IBI_MDB BIT(3)
#define DEV_CHAR_WEN BIT(8)
#define DEV_CHAR_ADDR(x) (((x) & GENMASK(6, 0)) << 9)

#define REG_OPS_SET_SG(x) ((x) << 5)
#define REG_OPS_PP_SG_MASK GENMASK(6, 5)

#define REG_IBI_CONFIG_LISTEN BIT(1)
#define REG_IBI_CONFIG_ENABLE BIT(0)

enum speed_grade {PP_SG_UNSET, PP_SG_1MHZ, PP_SG_3MHZ, PP_SG_6MHZ, PP_SG_12MHZ};
struct adi_i3c_cmd {
u32 cmd0;
Expand All @@ -109,6 +112,11 @@ struct adi_i3c_master {
struct i3c_master_controller base;
u32 free_rr_slots;
unsigned int maxdevs;
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;
Expand Down Expand Up @@ -186,6 +194,9 @@ static bool adi_i3c_master_supports_ccc_cmd(struct i3c_master_controller *m,

static int adi_i3c_master_disable(struct adi_i3c_master *master)
{
writel(~REG_IBI_CONFIG_LISTEN | ~REG_IBI_CONFIG_ENABLE,
master->regs + REG_IBI_CONFIG);

return 0;
}

Expand Down Expand Up @@ -437,6 +448,8 @@ static int adi_i3c_master_priv_xfers(struct i3c_dev_desc *dev,

struct adi_i3c_i2c_dev_data {
u16 id;
s16 ibi;
struct i3c_generic_ibi_pool *ibi_pool;
};

static int adi_i3c_master_get_rr_slot(struct adi_i3c_master *master,
Expand Down Expand Up @@ -487,6 +500,7 @@ static int adi_i3c_master_attach_i3c_dev(struct i3c_dev_desc *dev)
return slot;
}

data->ibi = -1;
data->id = slot;
i3c_dev_set_master_data(dev, data);
master->free_rr_slots &= ~BIT(slot);
Expand All @@ -501,6 +515,22 @@ static int adi_i3c_master_attach_i3c_dev(struct i3c_dev_desc *dev)
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;
u8 addr;

i3c_bus_for_each_i3cdev(&m->bus, i3cdev) {
addr = i3cdev->info.dyn_addr ?
i3cdev->info.dyn_addr : i3cdev->info.static_addr;
writel(DEV_CHAR_ADDR(addr), master->regs + REG_DEV_CHAR);
writel(readl(master->regs + REG_DEV_CHAR) |
DEV_CHAR_BCR_IBI(i3cdev->info.bcr) | 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);
Expand Down Expand Up @@ -581,6 +611,7 @@ static void adi_i3c_master_upd_i3c_scl_lim(struct adi_i3c_master *master)

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;
Expand Down Expand Up @@ -634,6 +665,7 @@ static int adi_i3c_master_do_daa(struct i3c_master_controller *m)
{
struct adi_i3c_master *master = to_adi_i3c_master(m);
int ret;
u32 irq_mask;

master->daa.index = 0x8;
for (u8 i = 0; i < MAX_DEVS; i++) {
Expand All @@ -647,13 +679,14 @@ static int adi_i3c_master_do_daa(struct i3c_master_controller *m)
/* Will be reused as index for daa.addrs */
master->daa.index = 0;

/* Enable CMDR and DA IRQ */
writel(IRQ_PENDING_DAA_PENDING | IRQ_PENDING_CMDR_PENDING, master->regs + REG_IRQ_MASK);
irq_mask = readl(master->regs + REG_IRQ_MASK);
writel(irq_mask | IRQ_PENDING_DAA_PENDING,
master->regs + REG_IRQ_MASK);

//sleep(3000);
ret = i3c_master_entdaa_locked(&master->base);

/* Disable DA IRQ */
writel(IRQ_PENDING_CMDR_PENDING, master->regs + REG_IRQ_MASK);
writel(irq_mask, master->regs + REG_IRQ_MASK);

/* DAA always finishes with CE2_ERROR or NACK_RESP */
if (ret && ret != I3C_ERROR_M2)
Expand All @@ -662,6 +695,8 @@ static int adi_i3c_master_do_daa(struct i3c_master_controller *m)
/* 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);

Expand All @@ -685,9 +720,64 @@ static int adi_i3c_master_bus_init(struct i3c_master_controller *m)
if (ret)
return ret;

writel(REG_IBI_CONFIG_LISTEN | ~REG_IBI_CONFIG_ENABLE,
master->regs + REG_IBI_CONFIG);

return 0;
}

static void adi_i3c_master_handle_ibi(struct adi_i3c_master *master,
u32 ibi)
{
struct adi_i3c_i2c_dev_data *data;
bool data_consumed = false;
struct i3c_ibi_slot *slot;
u32 id;
struct i3c_dev_desc *dev;
u8 *buf;

id = adi_i3c_master_get_rr_slot(master, (ibi >> 17) & GENMASK(6, 0));
for (id = 0; id < master->ibi.num_slots; id++) {
if (master->ibi.slots[id] &&
master->ibi.slots[id]->info.dyn_addr == ((ibi >> 17) & GENMASK(6, 0)))
break;
}

if (id >= master->ibi.num_slots)
return;

dev = master->ibi.slots[id];
spin_lock(&master->ibi.lock);

data = i3c_dev_get_master_data(dev);
slot = i3c_generic_ibi_get_free_slot(data->ibi_pool);
if (!slot)
goto out_unlock;

buf = slot->data;
buf[0] = (ibi >> 8) & GENMASK(7, 0);

slot->len = 1;
i3c_master_queue_ibi(dev, slot);
data_consumed = true;

out_unlock:
spin_unlock(&master->ibi.lock);
}

static void adi_i3c_master_demux_ibis(struct adi_i3c_master *master)
{
u32 status0;

for (status0 = readl(master->regs + REG_FIFO_STATUS);
!(status0 & FIFO_STATUS_IBI_EMPTY);
status0 = readl(master->regs + REG_FIFO_STATUS)) {
u32 ibi = readl(master->regs + REG_IBI_FIFO);

adi_i3c_master_handle_ibi(master, ibi);
}
}

static int adi_i3c_master_handle_da_req(struct adi_i3c_master *master)
{
int addr;
Expand Down Expand Up @@ -723,6 +813,11 @@ static irqreturn_t adi_i3c_master_irq(int irq, void *data)
adi_i3c_master_end_xfer_locked(master, pending);
spin_unlock(&master->xferqueue.lock);

if (pending & IRQ_PENDING_IBI_PENDING) {
adi_i3c_master_demux_ibis(master);
clear |= IRQ_PENDING_IBI_PENDING;
}

if (pending & IRQ_PENDING_DAA_PENDING) {
if (!adi_i3c_master_handle_da_req(master))
clear |= IRQ_PENDING_DAA_PENDING;
Expand Down Expand Up @@ -785,6 +880,96 @@ static int adi_i3c_master_i2c_xfers(struct i2c_dev_desc *dev,
return 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);
int ret;

ret = i3c_master_disec_locked(m, dev->info.dyn_addr,
I3C_CCC_EVENT_SIR);

writel(REG_IBI_CONFIG_LISTEN | ~REG_IBI_CONFIG_ENABLE,
master->regs + REG_IBI_CONFIG);

writel(readl(master->regs + REG_IRQ_MASK) | ~IRQ_PENDING_IBI_PENDING,
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);
int ret;

writel(REG_IBI_CONFIG_LISTEN | REG_IBI_CONFIG_ENABLE,
master->regs + REG_IBI_CONFIG);

writel(readl(master->regs + REG_IRQ_MASK) | IRQ_PENDING_IBI_PENDING,
master->regs + REG_IRQ_MASK);

ret = i3c_master_enec_locked(m, dev->info.dyn_addr,
I3C_CCC_EVENT_SIR);
return ret;
}

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 = i3c_dev_get_master_data(dev);
unsigned long flags;
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_irqsave(&master->ibi.lock, flags);
for (i = 0; i < master->ibi.num_slots; i++) {
if (!master->ibi.slots[i]) {
data->ibi = i;
master->ibi.slots[i] = dev;
break;
}
}
spin_unlock_irqrestore(&master->ibi.lock, flags);

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);
unsigned long flags;

spin_lock_irqsave(&master->ibi.lock, flags);
master->ibi.slots[data->ibi] = NULL;
data->ibi = -1;
spin_unlock_irqrestore(&master->ibi.lock, flags);

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,
Expand All @@ -798,6 +983,11 @@ static const struct i3c_master_controller_ops adi_i3c_master_ops = {
.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[] = {
Expand Down Expand Up @@ -857,6 +1047,16 @@ static int adi_i3c_master_probe(struct platform_device *pdev)

writel(IRQ_PENDING_CMDR_PENDING, 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) {
ret = -ENOMEM;
goto err_clk_disable;
}

ret = i3c_master_register(&master->base, &pdev->dev,
&adi_i3c_master_ops, false);
if (ret)
Expand Down

0 comments on commit a9e0da2

Please sign in to comment.