Commit 750909d5 authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull i3c updates from Alexandre Belloni:
 "Core:
   - avoid possible deadlock on probe
   - ensured preferred address is used on hot-join

  Drivers:
   - dw: add AMD I3C controller support
   - mipi-i3c-hci: fix SETDASA, DMA interrupts fixes
   - svc: many fixes for IBI and hotjoin"

* tag 'i3c/for-6.13' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux:
  i3c: Use i3cdev->desc->info instead of calling i3c_device_get_info() to avoid deadlock
  i3c: mipi-i3c-hci: Support SETDASA CCC
  i3c: dw: Add quirk to address OD/PP timing issue on AMD platform
  i3c: dw: Add support for AMDI0015 ACPI ID
  i3c: master: svc: Modify enabled_events bit 7:0 to act as IBI enable counter
  i3c: Document I3C_ADDR_SLOT_EXT_STATUS_MASK
  i3c: master: svc: Fix pm_runtime_set_suspended() with runtime pm enabled
  i3c: mipi-i3c-hci: Handle interrupts according to current specifications
  i3c: mipi-i3c-hci: Mask ring interrupts before ring stop request
  i3c: master: Fix miss free init_dyn_addr at i3c_master_put_i3c_addrs()
  i3c: master: Remove i3c_dev_disable_ibi_locked(olddev) on device hotjoin
  i3c: master: svc: fix possible assignment of the same address to two devices
  i3c: master: svc: wait for Manual ACK/NACK Done before next step
  i3c: master: svc: use spin_lock_irqsave at svc_i3c_master_ibi_work()
  i3c: master: svc: need check IBIWON for dynamic address assignment
  i3c: master: svc: manually emit NACK/ACK for hotjoin
  i3c: master: svc: use repeat start when IBI WIN happens
  i3c: master: Fix dynamic address leak when 'assigned-address' is present
  i3c: master: Extend address status bit to 4 and add I3C_ADDR_SLOT_EXT_DESIRED
  i3c: master: Replace hard code 2 with macro I3C_ADDR_SLOT_STATUS_BITS
parents 1746db26 6cf7b65f
Loading
Loading
Loading
Loading
+72 −28
Original line number Diff line number Diff line
@@ -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)
{
	int bitpos = addr * 2;
	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 * 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,
		/* 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_I3C_DEV);
						  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.
+34 −0
Original line number Diff line number Diff line
@@ -220,6 +220,14 @@

#define XFER_TIMEOUT (msecs_to_jiffies(1000))
#define RPM_AUTOSUSPEND_TIMEOUT 1000 /* ms */

/* Timing values to configure 12.5MHz frequency */
#define AMD_I3C_OD_TIMING          0x4C007C
#define AMD_I3C_PP_TIMING          0x8001A

/* List of quirks */
#define AMD_I3C_OD_PP_TIMING		BIT(1)

struct dw_i3c_cmd {
	u32 cmd_lo;
	u32 cmd_hi;
@@ -794,6 +802,12 @@ static int dw_i3c_ccc_get(struct dw_i3c_master *master, struct i3c_ccc_cmd *ccc)
	return ret;
}

static void amd_configure_od_pp_quirk(struct dw_i3c_master *master)
{
	master->i3c_od_timing = AMD_I3C_OD_TIMING;
	master->i3c_pp_timing = AMD_I3C_PP_TIMING;
}

static int dw_i3c_master_send_ccc_cmd(struct i3c_master_controller *m,
				      struct i3c_ccc_cmd *ccc)
{
@@ -803,6 +817,13 @@ static int dw_i3c_master_send_ccc_cmd(struct i3c_master_controller *m,
	if (ccc->id == I3C_CCC_ENTDAA)
		return -EINVAL;

	/* AMD platform specific OD and PP timings */
	if (master->quirks & AMD_I3C_OD_PP_TIMING) {
		amd_configure_od_pp_quirk(master);
		writel(master->i3c_pp_timing, master->regs + SCL_I3C_PP_TIMING);
		writel(master->i3c_od_timing, master->regs + SCL_I3C_OD_TIMING);
	}

	ret = pm_runtime_resume_and_get(master->dev);
	if (ret < 0) {
		dev_err(master->dev,
@@ -1602,6 +1623,8 @@ int dw_i3c_common_probe(struct dw_i3c_master *master,
	master->maxdevs = ret >> 16;
	master->free_pos = GENMASK(master->maxdevs - 1, 0);

	master->quirks = (unsigned long)device_get_match_data(&pdev->dev);

	INIT_WORK(&master->hj_work, dw_i3c_hj_work);
	ret = i3c_master_register(&master->base, &pdev->dev,
				  &dw_mipi_i3c_ops, false);
@@ -1675,6 +1698,10 @@ static void dw_i3c_master_restore_addrs(struct dw_i3c_master *master)

static void dw_i3c_master_restore_timing_regs(struct dw_i3c_master *master)
{
	/* AMD platform specific OD and PP timings */
	if (master->quirks & AMD_I3C_OD_PP_TIMING)
		amd_configure_od_pp_quirk(master);

	writel(master->i3c_pp_timing, master->regs + SCL_I3C_PP_TIMING);
	writel(master->bus_free_timing, master->regs + BUS_FREE_TIMING);
	writel(master->i3c_od_timing, master->regs + SCL_I3C_OD_TIMING);
@@ -1748,12 +1775,19 @@ static const struct of_device_id dw_i3c_master_of_match[] = {
};
MODULE_DEVICE_TABLE(of, dw_i3c_master_of_match);

static const struct acpi_device_id amd_i3c_device_match[] = {
	{ "AMDI0015", AMD_I3C_OD_PP_TIMING },
	{ }
};
MODULE_DEVICE_TABLE(acpi, amd_i3c_device_match);

static struct platform_driver dw_i3c_driver = {
	.probe = dw_i3c_probe,
	.remove_new = dw_i3c_remove,
	.driver = {
		.name = "dw-i3c-master",
		.of_match_table = dw_i3c_master_of_match,
		.acpi_match_table = amd_i3c_device_match,
		.pm = &dw_i3c_pm_ops,
	},
};
+1 −0
Original line number Diff line number Diff line
@@ -50,6 +50,7 @@ struct dw_i3c_master {
	u32 bus_free_timing;
	u32 i2c_fm_timing;
	u32 i2c_fmp_timing;
	u32 quirks;
	/*
	 * Per-device hardware data, used to manage the device address table
	 * (DAT)
+5 −14
Original line number Diff line number Diff line
@@ -80,8 +80,6 @@
#define INTR_HC_CMD_SEQ_UFLOW_STAT	BIT(12)	/* Cmd Sequence Underflow */
#define INTR_HC_RESET_CANCEL		BIT(11)	/* HC Cancelled Reset */
#define INTR_HC_INTERNAL_ERR		BIT(10)	/* HC Internal Error */
#define INTR_HC_PIO			BIT(8)	/* cascaded PIO interrupt */
#define INTR_HC_RINGS			GENMASK(7, 0)

#define DAT_SECTION			0x30	/* Device Address Table */
#define DAT_ENTRY_SIZE			GENMASK(31, 28)
@@ -438,7 +436,8 @@ static int i3c_hci_attach_i3c_dev(struct i3c_dev_desc *dev)
			kfree(dev_data);
			return ret;
		}
		mipi_i3c_hci_dat_v1.set_dynamic_addr(hci, ret, dev->info.dyn_addr);
		mipi_i3c_hci_dat_v1.set_dynamic_addr(hci, ret,
						     dev->info.dyn_addr ?: dev->info.static_addr);
		dev_data->dat_idx = ret;
	}
	i3c_dev_set_master_data(dev, dev_data);
@@ -597,9 +596,6 @@ static irqreturn_t i3c_hci_irq_handler(int irq, void *dev_id)

	if (val) {
		reg_write(INTR_STATUS, val);
	} else {
		/* v1.0 does not have PIO cascaded notification bits */
		val |= INTR_HC_PIO;
	}

	if (val & INTR_HC_RESET_CANCEL) {
@@ -610,14 +606,9 @@ static irqreturn_t i3c_hci_irq_handler(int irq, void *dev_id)
		dev_err(&hci->master.dev, "Host Controller Internal Error\n");
		val &= ~INTR_HC_INTERNAL_ERR;
	}
	if (val & INTR_HC_PIO) {
		hci->io->irq_handler(hci, 0);
		val &= ~INTR_HC_PIO;
	}
	if (val & INTR_HC_RINGS) {
		hci->io->irq_handler(hci, val & INTR_HC_RINGS);
		val &= ~INTR_HC_RINGS;
	}

	hci->io->irq_handler(hci);

	if (val)
		dev_err(&hci->master.dev, "unexpected INTR_STATUS %#x\n", val);
	else
+3 −7
Original line number Diff line number Diff line
@@ -159,10 +159,10 @@ static void hci_dma_cleanup(struct i3c_hci *hci)
	for (i = 0; i < rings->total; i++) {
		rh = &rings->headers[i];

		rh_reg_write(INTR_SIGNAL_ENABLE, 0);
		rh_reg_write(RING_CONTROL, 0);
		rh_reg_write(CR_SETUP, 0);
		rh_reg_write(IBI_SETUP, 0);
		rh_reg_write(INTR_SIGNAL_ENABLE, 0);

		if (rh->xfer)
			dma_free_coherent(&hci->master.dev,
@@ -733,20 +733,16 @@ static void hci_dma_process_ibi(struct i3c_hci *hci, struct hci_rh_data *rh)
	rh_reg_write(CHUNK_CONTROL, rh_reg_read(CHUNK_CONTROL) + ibi_chunks);
}

static bool hci_dma_irq_handler(struct i3c_hci *hci, unsigned int mask)
static bool hci_dma_irq_handler(struct i3c_hci *hci)
{
	struct hci_rings_data *rings = hci->io_data;
	unsigned int i;
	bool handled = false;

	for (i = 0; mask && i < rings->total; i++) {
	for (i = 0; i < rings->total; i++) {
		struct hci_rh_data *rh;
		u32 status;

		if (!(mask & BIT(i)))
			continue;
		mask &= ~BIT(i);

		rh = &rings->headers[i];
		status = rh_reg_read(INTR_STATUS);
		DBG("rh%d status: %#x", i, status);
Loading