diff options
Diffstat (limited to 'drivers/i3c/master.c')
-rw-r--r-- | drivers/i3c/master.c | 100 |
1 files changed, 72 insertions, 28 deletions
diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 6f3eb710a75d..42310c9a00c2 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -282,7 +282,8 @@ static int i3c_device_uevent(const struct device *dev, struct kobj_uevent_env *e struct i3c_device_info devinfo; u16 manuf, part, ext; - i3c_device_get_info(i3cdev, &devinfo); + if (i3cdev->desc) + devinfo = i3cdev->desc->info; manuf = I3C_PID_MANUF_ID(devinfo.pid); part = I3C_PID_PART_ID(devinfo.pid); ext = I3C_PID_EXTRA_INFO(devinfo.pid); @@ -345,10 +346,10 @@ const struct bus_type i3c_bus_type = { EXPORT_SYMBOL_GPL(i3c_bus_type); static enum i3c_addr_slot_status -i3c_bus_get_addr_slot_status(struct i3c_bus *bus, u16 addr) +i3c_bus_get_addr_slot_status_mask(struct i3c_bus *bus, u16 addr, u32 mask) { unsigned long status; - int bitpos = addr * 2; + int bitpos = addr * I3C_ADDR_SLOT_STATUS_BITS; if (addr > I2C_MAX_ADDR) return I3C_ADDR_SLOT_RSVD; @@ -356,22 +357,33 @@ i3c_bus_get_addr_slot_status(struct i3c_bus *bus, u16 addr) status = bus->addrslots[bitpos / BITS_PER_LONG]; status >>= bitpos % BITS_PER_LONG; - return status & I3C_ADDR_SLOT_STATUS_MASK; + return status & mask; } -static void i3c_bus_set_addr_slot_status(struct i3c_bus *bus, u16 addr, - enum i3c_addr_slot_status status) +static enum i3c_addr_slot_status +i3c_bus_get_addr_slot_status(struct i3c_bus *bus, u16 addr) +{ + return i3c_bus_get_addr_slot_status_mask(bus, addr, I3C_ADDR_SLOT_STATUS_MASK); +} + +static void i3c_bus_set_addr_slot_status_mask(struct i3c_bus *bus, u16 addr, + enum i3c_addr_slot_status status, u32 mask) { - int bitpos = addr * 2; + int bitpos = addr * I3C_ADDR_SLOT_STATUS_BITS; unsigned long *ptr; if (addr > I2C_MAX_ADDR) return; ptr = bus->addrslots + (bitpos / BITS_PER_LONG); - *ptr &= ~((unsigned long)I3C_ADDR_SLOT_STATUS_MASK << - (bitpos % BITS_PER_LONG)); - *ptr |= (unsigned long)status << (bitpos % BITS_PER_LONG); + *ptr &= ~((unsigned long)mask << (bitpos % BITS_PER_LONG)); + *ptr |= ((unsigned long)status & mask) << (bitpos % BITS_PER_LONG); +} + +static void i3c_bus_set_addr_slot_status(struct i3c_bus *bus, u16 addr, + enum i3c_addr_slot_status status) +{ + i3c_bus_set_addr_slot_status_mask(bus, addr, status, I3C_ADDR_SLOT_STATUS_MASK); } static bool i3c_bus_dev_addr_is_avail(struct i3c_bus *bus, u8 addr) @@ -383,13 +395,44 @@ static bool i3c_bus_dev_addr_is_avail(struct i3c_bus *bus, u8 addr) return status == I3C_ADDR_SLOT_FREE; } +/* + * ┌────┬─────────────┬───┬─────────┬───┐ + * │S/Sr│ 7'h7E RnW=0 │ACK│ ENTDAA │ T ├────┐ + * └────┴─────────────┴───┴─────────┴───┘ │ + * ┌─────────────────────────────────────────┘ + * │ ┌──┬─────────────┬───┬─────────────────┬────────────────┬───┬─────────┐ + * └─►│Sr│7'h7E RnW=1 │ACK│48bit UID BCR DCR│Assign 7bit Addr│PAR│ ACK/NACK│ + * └──┴─────────────┴───┴─────────────────┴────────────────┴───┴─────────┘ + * Some master controllers (such as HCI) need to prepare the entire above transaction before + * sending it out to the I3C bus. This means that a 7-bit dynamic address needs to be allocated + * before knowing the target device's UID information. + * + * However, some I3C targets may request specific addresses (called as "init_dyn_addr"), which is + * typically specified by the DT-'s assigned-address property. Lower addresses having higher IBI + * priority. If it is available, i3c_bus_get_free_addr() preferably return a free address that is + * not in the list of desired addresses (called as "init_dyn_addr"). This allows the device with + * the "init_dyn_addr" to switch to its "init_dyn_addr" when it hot-joins the I3C bus. Otherwise, + * if the "init_dyn_addr" is already in use by another I3C device, the target device will not be + * able to switch to its desired address. + * + * If the previous step fails, fallback returning one of the remaining unassigned address, + * regardless of its state in the desired list. + */ static int i3c_bus_get_free_addr(struct i3c_bus *bus, u8 start_addr) { enum i3c_addr_slot_status status; u8 addr; for (addr = start_addr; addr < I3C_MAX_ADDR; addr++) { - status = i3c_bus_get_addr_slot_status(bus, addr); + status = i3c_bus_get_addr_slot_status_mask(bus, addr, + I3C_ADDR_SLOT_EXT_STATUS_MASK); + if (status == I3C_ADDR_SLOT_FREE) + return addr; + } + + for (addr = start_addr; addr < I3C_MAX_ADDR; addr++) { + status = i3c_bus_get_addr_slot_status_mask(bus, addr, + I3C_ADDR_SLOT_STATUS_MASK); if (status == I3C_ADDR_SLOT_FREE) return addr; } @@ -1417,7 +1460,7 @@ static void i3c_master_put_i3c_addrs(struct i3c_dev_desc *dev) I3C_ADDR_SLOT_FREE); if (dev->boardinfo && dev->boardinfo->init_dyn_addr) - i3c_bus_set_addr_slot_status(&master->bus, dev->info.dyn_addr, + i3c_bus_set_addr_slot_status(&master->bus, dev->boardinfo->init_dyn_addr, I3C_ADDR_SLOT_FREE); } @@ -1506,16 +1549,9 @@ static int i3c_master_reattach_i3c_dev(struct i3c_dev_desc *dev, u8 old_dyn_addr) { struct i3c_master_controller *master = i3c_dev_get_master(dev); - enum i3c_addr_slot_status status; int ret; - if (dev->info.dyn_addr != old_dyn_addr && - (!dev->boardinfo || - dev->info.dyn_addr != dev->boardinfo->init_dyn_addr)) { - status = i3c_bus_get_addr_slot_status(&master->bus, - dev->info.dyn_addr); - if (status != I3C_ADDR_SLOT_FREE) - return -EBUSY; + if (dev->info.dyn_addr != old_dyn_addr) { i3c_bus_set_addr_slot_status(&master->bus, dev->info.dyn_addr, I3C_ADDR_SLOT_I3C_DEV); @@ -1918,9 +1954,11 @@ static int i3c_master_bus_init(struct i3c_master_controller *master) goto err_rstdaa; } - i3c_bus_set_addr_slot_status(&master->bus, - i3cboardinfo->init_dyn_addr, - I3C_ADDR_SLOT_I3C_DEV); + /* Do not mark as occupied until real device exist in bus */ + i3c_bus_set_addr_slot_status_mask(&master->bus, + i3cboardinfo->init_dyn_addr, + I3C_ADDR_SLOT_EXT_DESIRED, + I3C_ADDR_SLOT_EXT_STATUS_MASK); /* * Only try to create/attach devices that have a static @@ -2051,11 +2089,16 @@ int i3c_master_add_i3c_dev_locked(struct i3c_master_controller *master, ibireq.max_payload_len = olddev->ibi->max_payload_len; ibireq.num_slots = olddev->ibi->num_slots; - if (olddev->ibi->enabled) { + if (olddev->ibi->enabled) enable_ibi = true; - i3c_dev_disable_ibi_locked(olddev); - } - + /* + * The olddev should not receive any commands on the + * i3c bus as it does not exist and has been assigned + * a new address. This will result in NACK or timeout. + * So, update the olddev->ibi->enabled flag to false + * to avoid DISEC with OldAddr. + */ + olddev->ibi->enabled = false; i3c_dev_free_ibi_locked(olddev); } mutex_unlock(&olddev->ibi_lock); @@ -2083,7 +2126,8 @@ int i3c_master_add_i3c_dev_locked(struct i3c_master_controller *master, else expected_dyn_addr = newdev->info.dyn_addr; - if (newdev->info.dyn_addr != expected_dyn_addr) { + if (newdev->info.dyn_addr != expected_dyn_addr && + i3c_bus_get_addr_slot_status(&master->bus, expected_dyn_addr) == I3C_ADDR_SLOT_FREE) { /* * Try to apply the expected dynamic address. If it fails, keep * the address assigned by the master. |