Merge tag 'i3c/for-6.8' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux
Pull i3c updates from Alexandre Belloni: "We are continuing to see more fixes as hardware is available and code is actually getting tested. Core: - Add a sysfs control for hotjoin - Add fallback method for GETMXDS CCC Drivers: - cdns: fix prescale for i2c clock - mipi-i3c-hci: more fixes now that the driver is used - svc: hotjoin enabling/disabling support" * tag 'i3c/for-6.8' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux: i3c: document hotjoin sysfs entry i3c: master: fix kernel-doc check warning i3c: master: cdns: Update maximum prescaler value for i2c clock i3c: master: fix Excess kernel-doc description warning i3c: master: svc: return actual transfer data len i3c: master: svc: rename read_len as actual_len i3c: add actual_len in i3c_priv_xfer i3c: master: svc: add hot join support i3c: master: add enable(disable) hot join in sys entry i3c: master: Fix build error i3c: Add fallback method for GETMXDS CCC i3c: mipi-i3c-hci: Add DMA bounce buffer for private transfers i3c: mipi-i3c-hci: Handle I3C address header error in hci_cmd_v1_daa() i3c: mipi-i3c-hci: Do not overallocate transfers in hci_cmd_v1_daa() i3c: mipi-i3c-hci: Report NACK response from CCC command to core
This commit is contained in:
@@ -88,6 +88,21 @@ Description:
|
|||||||
This entry describes the HDRCAP of the master controller
|
This entry describes the HDRCAP of the master controller
|
||||||
driving the bus.
|
driving the bus.
|
||||||
|
|
||||||
|
What: /sys/bus/i3c/devices/i3c-<bus-id>/hotjoin
|
||||||
|
KernelVersion: 6.8
|
||||||
|
Contact: linux-i3c@vger.kernel.org
|
||||||
|
Description:
|
||||||
|
I3C’s Hot-Join mechanism allows an I3C Device to inform the
|
||||||
|
Active Controller that a newly-joined Target is present on the
|
||||||
|
I3C Bus and is ready to receive a Dynamic Address, in order to
|
||||||
|
become fully functional on the Bus. Hot-Join is used when the
|
||||||
|
Target is mounted on the same I3C bus and remains depowered
|
||||||
|
until needed or until the Target is physically inserted into the
|
||||||
|
I3C bus
|
||||||
|
|
||||||
|
This entry allows to enable or disable Hot-join of the Current
|
||||||
|
Controller driving the bus.
|
||||||
|
|
||||||
What: /sys/bus/i3c/devices/i3c-<bus-id>/<bus-id>-<device-pid>
|
What: /sys/bus/i3c/devices/i3c-<bus-id>/<bus-id>-<device-pid>
|
||||||
KernelVersion: 5.0
|
KernelVersion: 5.0
|
||||||
Contact: linux-i3c@vger.kernel.org
|
Contact: linux-i3c@vger.kernel.org
|
||||||
|
|||||||
+93
-2
@@ -557,6 +557,88 @@ static ssize_t i2c_scl_frequency_show(struct device *dev,
|
|||||||
}
|
}
|
||||||
static DEVICE_ATTR_RO(i2c_scl_frequency);
|
static DEVICE_ATTR_RO(i2c_scl_frequency);
|
||||||
|
|
||||||
|
static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!master || !master->ops)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (!master->ops->enable_hotjoin || !master->ops->disable_hotjoin)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
i3c_bus_normaluse_lock(&master->bus);
|
||||||
|
|
||||||
|
if (enable)
|
||||||
|
ret = master->ops->enable_hotjoin(master);
|
||||||
|
else
|
||||||
|
ret = master->ops->disable_hotjoin(master);
|
||||||
|
|
||||||
|
master->hotjoin = enable;
|
||||||
|
|
||||||
|
i3c_bus_normaluse_unlock(&master->bus);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t hotjoin_store(struct device *dev, struct device_attribute *attr,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
struct i3c_bus *i3cbus = dev_to_i3cbus(dev);
|
||||||
|
int ret;
|
||||||
|
bool res;
|
||||||
|
|
||||||
|
if (!i3cbus->cur_master)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (kstrtobool(buf, &res))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
ret = i3c_set_hotjoin(i3cbus->cur_master->common.master, res);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* i3c_master_enable_hotjoin - Enable hotjoin
|
||||||
|
* @master: I3C master object
|
||||||
|
*
|
||||||
|
* Return: a 0 in case of success, an negative error code otherwise.
|
||||||
|
*/
|
||||||
|
int i3c_master_enable_hotjoin(struct i3c_master_controller *master)
|
||||||
|
{
|
||||||
|
return i3c_set_hotjoin(master, true);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(i3c_master_enable_hotjoin);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* i3c_master_disable_hotjoin - Disable hotjoin
|
||||||
|
* @master: I3C master object
|
||||||
|
*
|
||||||
|
* Return: a 0 in case of success, an negative error code otherwise.
|
||||||
|
*/
|
||||||
|
int i3c_master_disable_hotjoin(struct i3c_master_controller *master)
|
||||||
|
{
|
||||||
|
return i3c_set_hotjoin(master, false);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(i3c_master_disable_hotjoin);
|
||||||
|
|
||||||
|
static ssize_t hotjoin_show(struct device *dev, struct device_attribute *da, char *buf)
|
||||||
|
{
|
||||||
|
struct i3c_bus *i3cbus = dev_to_i3cbus(dev);
|
||||||
|
ssize_t ret;
|
||||||
|
|
||||||
|
i3c_bus_normaluse_lock(i3cbus);
|
||||||
|
ret = sysfs_emit(buf, "%d\n", i3cbus->cur_master->common.master->hotjoin);
|
||||||
|
i3c_bus_normaluse_unlock(i3cbus);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEVICE_ATTR_RW(hotjoin);
|
||||||
|
|
||||||
static struct attribute *i3c_masterdev_attrs[] = {
|
static struct attribute *i3c_masterdev_attrs[] = {
|
||||||
&dev_attr_mode.attr,
|
&dev_attr_mode.attr,
|
||||||
&dev_attr_current_master.attr,
|
&dev_attr_current_master.attr,
|
||||||
@@ -567,6 +649,7 @@ static struct attribute *i3c_masterdev_attrs[] = {
|
|||||||
&dev_attr_pid.attr,
|
&dev_attr_pid.attr,
|
||||||
&dev_attr_dynamic_address.attr,
|
&dev_attr_dynamic_address.attr,
|
||||||
&dev_attr_hdrcap.attr,
|
&dev_attr_hdrcap.attr,
|
||||||
|
&dev_attr_hotjoin.attr,
|
||||||
NULL,
|
NULL,
|
||||||
};
|
};
|
||||||
ATTRIBUTE_GROUPS(i3c_masterdev);
|
ATTRIBUTE_GROUPS(i3c_masterdev);
|
||||||
@@ -1130,8 +1213,16 @@ static int i3c_master_getmxds_locked(struct i3c_master_controller *master,
|
|||||||
|
|
||||||
i3c_ccc_cmd_init(&cmd, true, I3C_CCC_GETMXDS, &dest, 1);
|
i3c_ccc_cmd_init(&cmd, true, I3C_CCC_GETMXDS, &dest, 1);
|
||||||
ret = i3c_master_send_ccc_cmd_locked(master, &cmd);
|
ret = i3c_master_send_ccc_cmd_locked(master, &cmd);
|
||||||
if (ret)
|
if (ret) {
|
||||||
goto out;
|
/*
|
||||||
|
* Retry when the device does not support max read turnaround
|
||||||
|
* while expecting shorter length from this CCC command.
|
||||||
|
*/
|
||||||
|
dest.payload.len -= 3;
|
||||||
|
ret = i3c_master_send_ccc_cmd_locked(master, &cmd);
|
||||||
|
if (ret)
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
if (dest.payload.len != 2 && dest.payload.len != 5) {
|
if (dest.payload.len != 2 && dest.payload.len != 5) {
|
||||||
ret = -EIO;
|
ret = -EIO;
|
||||||
|
|||||||
@@ -76,7 +76,8 @@
|
|||||||
#define PRESCL_CTRL0 0x14
|
#define PRESCL_CTRL0 0x14
|
||||||
#define PRESCL_CTRL0_I2C(x) ((x) << 16)
|
#define PRESCL_CTRL0_I2C(x) ((x) << 16)
|
||||||
#define PRESCL_CTRL0_I3C(x) (x)
|
#define PRESCL_CTRL0_I3C(x) (x)
|
||||||
#define PRESCL_CTRL0_MAX GENMASK(9, 0)
|
#define PRESCL_CTRL0_I3C_MAX GENMASK(9, 0)
|
||||||
|
#define PRESCL_CTRL0_I2C_MAX GENMASK(15, 0)
|
||||||
|
|
||||||
#define PRESCL_CTRL1 0x18
|
#define PRESCL_CTRL1 0x18
|
||||||
#define PRESCL_CTRL1_PP_LOW_MASK GENMASK(15, 8)
|
#define PRESCL_CTRL1_PP_LOW_MASK GENMASK(15, 8)
|
||||||
@@ -1233,7 +1234,7 @@ static int cdns_i3c_master_bus_init(struct i3c_master_controller *m)
|
|||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
pres = DIV_ROUND_UP(sysclk_rate, (bus->scl_rate.i3c * 4)) - 1;
|
pres = DIV_ROUND_UP(sysclk_rate, (bus->scl_rate.i3c * 4)) - 1;
|
||||||
if (pres > PRESCL_CTRL0_MAX)
|
if (pres > PRESCL_CTRL0_I3C_MAX)
|
||||||
return -ERANGE;
|
return -ERANGE;
|
||||||
|
|
||||||
bus->scl_rate.i3c = sysclk_rate / ((pres + 1) * 4);
|
bus->scl_rate.i3c = sysclk_rate / ((pres + 1) * 4);
|
||||||
@@ -1246,7 +1247,7 @@ static int cdns_i3c_master_bus_init(struct i3c_master_controller *m)
|
|||||||
max_i2cfreq = bus->scl_rate.i2c;
|
max_i2cfreq = bus->scl_rate.i2c;
|
||||||
|
|
||||||
pres = (sysclk_rate / (max_i2cfreq * 5)) - 1;
|
pres = (sysclk_rate / (max_i2cfreq * 5)) - 1;
|
||||||
if (pres > PRESCL_CTRL0_MAX)
|
if (pres > PRESCL_CTRL0_I2C_MAX)
|
||||||
return -ERANGE;
|
return -ERANGE;
|
||||||
|
|
||||||
bus->scl_rate.i2c = sysclk_rate / ((pres + 1) * 5);
|
bus->scl_rate.i2c = sysclk_rate / ((pres + 1) * 5);
|
||||||
|
|||||||
@@ -298,7 +298,7 @@ static int hci_cmd_v1_daa(struct i3c_hci *hci)
|
|||||||
unsigned int dcr, bcr;
|
unsigned int dcr, bcr;
|
||||||
DECLARE_COMPLETION_ONSTACK(done);
|
DECLARE_COMPLETION_ONSTACK(done);
|
||||||
|
|
||||||
xfer = hci_alloc_xfer(2);
|
xfer = hci_alloc_xfer(1);
|
||||||
if (!xfer)
|
if (!xfer)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
@@ -339,12 +339,13 @@ static int hci_cmd_v1_daa(struct i3c_hci *hci)
|
|||||||
ret = -ETIME;
|
ret = -ETIME;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (RESP_STATUS(xfer[0].response) == RESP_ERR_NACK &&
|
if ((RESP_STATUS(xfer->response) == RESP_ERR_ADDR_HEADER ||
|
||||||
|
RESP_STATUS(xfer->response) == RESP_ERR_NACK) &&
|
||||||
RESP_DATA_LENGTH(xfer->response) == 1) {
|
RESP_DATA_LENGTH(xfer->response) == 1) {
|
||||||
ret = 0; /* no more devices to be assigned */
|
ret = 0; /* no more devices to be assigned */
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (RESP_STATUS(xfer[0].response) != RESP_SUCCESS) {
|
if (RESP_STATUS(xfer->response) != RESP_SUCCESS) {
|
||||||
ret = -EIO;
|
ret = -EIO;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -245,7 +245,14 @@ static int i3c_hci_send_ccc_cmd(struct i3c_master_controller *m,
|
|||||||
if (ccc->rnw)
|
if (ccc->rnw)
|
||||||
ccc->dests[i - prefixed].payload.len =
|
ccc->dests[i - prefixed].payload.len =
|
||||||
RESP_DATA_LENGTH(xfer[i].response);
|
RESP_DATA_LENGTH(xfer[i].response);
|
||||||
if (RESP_STATUS(xfer[i].response) != RESP_SUCCESS) {
|
switch (RESP_STATUS(xfer[i].response)) {
|
||||||
|
case RESP_SUCCESS:
|
||||||
|
continue;
|
||||||
|
case RESP_ERR_ADDR_HEADER:
|
||||||
|
case RESP_ERR_NACK:
|
||||||
|
ccc->err = I3C_ERROR_M2;
|
||||||
|
fallthrough;
|
||||||
|
default:
|
||||||
ret = -EIO;
|
ret = -EIO;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
@@ -269,6 +276,34 @@ static int i3c_hci_daa(struct i3c_master_controller *m)
|
|||||||
return hci->cmd->perform_daa(hci);
|
return hci->cmd->perform_daa(hci);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int i3c_hci_alloc_safe_xfer_buf(struct i3c_hci *hci,
|
||||||
|
struct hci_xfer *xfer)
|
||||||
|
{
|
||||||
|
if (hci->io != &mipi_i3c_hci_dma ||
|
||||||
|
xfer->data == NULL || !is_vmalloc_addr(xfer->data))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (xfer->rnw)
|
||||||
|
xfer->bounce_buf = kzalloc(xfer->data_len, GFP_KERNEL);
|
||||||
|
else
|
||||||
|
xfer->bounce_buf = kmemdup(xfer->data,
|
||||||
|
xfer->data_len, GFP_KERNEL);
|
||||||
|
|
||||||
|
return xfer->bounce_buf == NULL ? -ENOMEM : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void i3c_hci_free_safe_xfer_buf(struct i3c_hci *hci,
|
||||||
|
struct hci_xfer *xfer)
|
||||||
|
{
|
||||||
|
if (hci->io != &mipi_i3c_hci_dma || xfer->bounce_buf == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (xfer->rnw)
|
||||||
|
memcpy(xfer->data, xfer->bounce_buf, xfer->data_len);
|
||||||
|
|
||||||
|
kfree(xfer->bounce_buf);
|
||||||
|
}
|
||||||
|
|
||||||
static int i3c_hci_priv_xfers(struct i3c_dev_desc *dev,
|
static int i3c_hci_priv_xfers(struct i3c_dev_desc *dev,
|
||||||
struct i3c_priv_xfer *i3c_xfers,
|
struct i3c_priv_xfer *i3c_xfers,
|
||||||
int nxfers)
|
int nxfers)
|
||||||
@@ -302,6 +337,9 @@ static int i3c_hci_priv_xfers(struct i3c_dev_desc *dev,
|
|||||||
}
|
}
|
||||||
hci->cmd->prep_i3c_xfer(hci, dev, &xfer[i]);
|
hci->cmd->prep_i3c_xfer(hci, dev, &xfer[i]);
|
||||||
xfer[i].cmd_desc[0] |= CMD_0_ROC;
|
xfer[i].cmd_desc[0] |= CMD_0_ROC;
|
||||||
|
ret = i3c_hci_alloc_safe_xfer_buf(hci, &xfer[i]);
|
||||||
|
if (ret)
|
||||||
|
goto out;
|
||||||
}
|
}
|
||||||
last = i - 1;
|
last = i - 1;
|
||||||
xfer[last].cmd_desc[0] |= CMD_0_TOC;
|
xfer[last].cmd_desc[0] |= CMD_0_TOC;
|
||||||
@@ -325,6 +363,9 @@ static int i3c_hci_priv_xfers(struct i3c_dev_desc *dev,
|
|||||||
}
|
}
|
||||||
|
|
||||||
out:
|
out:
|
||||||
|
for (i = 0; i < nxfers; i++)
|
||||||
|
i3c_hci_free_safe_xfer_buf(hci, &xfer[i]);
|
||||||
|
|
||||||
hci_free_xfer(xfer, nxfers);
|
hci_free_xfer(xfer, nxfers);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -350,6 +391,9 @@ static int i3c_hci_i2c_xfers(struct i2c_dev_desc *dev,
|
|||||||
xfer[i].rnw = i2c_xfers[i].flags & I2C_M_RD;
|
xfer[i].rnw = i2c_xfers[i].flags & I2C_M_RD;
|
||||||
hci->cmd->prep_i2c_xfer(hci, dev, &xfer[i]);
|
hci->cmd->prep_i2c_xfer(hci, dev, &xfer[i]);
|
||||||
xfer[i].cmd_desc[0] |= CMD_0_ROC;
|
xfer[i].cmd_desc[0] |= CMD_0_ROC;
|
||||||
|
ret = i3c_hci_alloc_safe_xfer_buf(hci, &xfer[i]);
|
||||||
|
if (ret)
|
||||||
|
goto out;
|
||||||
}
|
}
|
||||||
last = i - 1;
|
last = i - 1;
|
||||||
xfer[last].cmd_desc[0] |= CMD_0_TOC;
|
xfer[last].cmd_desc[0] |= CMD_0_TOC;
|
||||||
@@ -371,6 +415,9 @@ static int i3c_hci_i2c_xfers(struct i2c_dev_desc *dev,
|
|||||||
}
|
}
|
||||||
|
|
||||||
out:
|
out:
|
||||||
|
for (i = 0; i < nxfers; i++)
|
||||||
|
i3c_hci_free_safe_xfer_buf(hci, &xfer[i]);
|
||||||
|
|
||||||
hci_free_xfer(xfer, nxfers);
|
hci_free_xfer(xfer, nxfers);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -362,6 +362,7 @@ static int hci_dma_queue_xfer(struct i3c_hci *hci,
|
|||||||
struct hci_rh_data *rh;
|
struct hci_rh_data *rh;
|
||||||
unsigned int i, ring, enqueue_ptr;
|
unsigned int i, ring, enqueue_ptr;
|
||||||
u32 op1_val, op2_val;
|
u32 op1_val, op2_val;
|
||||||
|
void *buf;
|
||||||
|
|
||||||
/* For now we only use ring 0 */
|
/* For now we only use ring 0 */
|
||||||
ring = 0;
|
ring = 0;
|
||||||
@@ -390,9 +391,10 @@ static int hci_dma_queue_xfer(struct i3c_hci *hci,
|
|||||||
|
|
||||||
/* 2nd and 3rd words of Data Buffer Descriptor Structure */
|
/* 2nd and 3rd words of Data Buffer Descriptor Structure */
|
||||||
if (xfer->data) {
|
if (xfer->data) {
|
||||||
|
buf = xfer->bounce_buf ? xfer->bounce_buf : xfer->data;
|
||||||
xfer->data_dma =
|
xfer->data_dma =
|
||||||
dma_map_single(&hci->master.dev,
|
dma_map_single(&hci->master.dev,
|
||||||
xfer->data,
|
buf,
|
||||||
xfer->data_len,
|
xfer->data_len,
|
||||||
xfer->rnw ?
|
xfer->rnw ?
|
||||||
DMA_FROM_DEVICE :
|
DMA_FROM_DEVICE :
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ struct hci_xfer {
|
|||||||
struct {
|
struct {
|
||||||
/* DMA specific */
|
/* DMA specific */
|
||||||
dma_addr_t data_dma;
|
dma_addr_t data_dma;
|
||||||
|
void *bounce_buf;
|
||||||
int ring_number;
|
int ring_number;
|
||||||
int ring_entry;
|
int ring_entry;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -128,13 +128,17 @@
|
|||||||
/* This parameter depends on the implementation and may be tuned */
|
/* This parameter depends on the implementation and may be tuned */
|
||||||
#define SVC_I3C_FIFO_SIZE 16
|
#define SVC_I3C_FIFO_SIZE 16
|
||||||
|
|
||||||
|
#define SVC_I3C_EVENT_IBI BIT(0)
|
||||||
|
#define SVC_I3C_EVENT_HOTJOIN BIT(1)
|
||||||
|
|
||||||
struct svc_i3c_cmd {
|
struct svc_i3c_cmd {
|
||||||
u8 addr;
|
u8 addr;
|
||||||
bool rnw;
|
bool rnw;
|
||||||
u8 *in;
|
u8 *in;
|
||||||
const void *out;
|
const void *out;
|
||||||
unsigned int len;
|
unsigned int len;
|
||||||
unsigned int read_len;
|
unsigned int actual_len;
|
||||||
|
struct i3c_priv_xfer *xfer;
|
||||||
bool continued;
|
bool continued;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -177,6 +181,7 @@ struct svc_i3c_regs_save {
|
|||||||
* @ibi.tbq_slot: To be queued IBI slot
|
* @ibi.tbq_slot: To be queued IBI slot
|
||||||
* @ibi.lock: IBI lock
|
* @ibi.lock: IBI lock
|
||||||
* @lock: Transfer lock, protect between IBI work thread and callbacks from master
|
* @lock: Transfer lock, protect between IBI work thread and callbacks from master
|
||||||
|
* @enabled_events: Bit masks for enable events (IBI, HotJoin).
|
||||||
*/
|
*/
|
||||||
struct svc_i3c_master {
|
struct svc_i3c_master {
|
||||||
struct i3c_master_controller base;
|
struct i3c_master_controller base;
|
||||||
@@ -206,6 +211,7 @@ struct svc_i3c_master {
|
|||||||
spinlock_t lock;
|
spinlock_t lock;
|
||||||
} ibi;
|
} ibi;
|
||||||
struct mutex lock;
|
struct mutex lock;
|
||||||
|
int enabled_events;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -220,6 +226,11 @@ struct svc_i3c_i2c_dev_data {
|
|||||||
struct i3c_generic_ibi_pool *ibi_pool;
|
struct i3c_generic_ibi_pool *ibi_pool;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static inline bool is_events_enabled(struct svc_i3c_master *master, u32 mask)
|
||||||
|
{
|
||||||
|
return !!(master->enabled_events & mask);
|
||||||
|
}
|
||||||
|
|
||||||
static bool svc_i3c_master_error(struct svc_i3c_master *master)
|
static bool svc_i3c_master_error(struct svc_i3c_master *master)
|
||||||
{
|
{
|
||||||
u32 mstatus, merrwarn;
|
u32 mstatus, merrwarn;
|
||||||
@@ -429,13 +440,16 @@ static void svc_i3c_master_ibi_work(struct work_struct *work)
|
|||||||
switch (ibitype) {
|
switch (ibitype) {
|
||||||
case SVC_I3C_MSTATUS_IBITYPE_IBI:
|
case SVC_I3C_MSTATUS_IBITYPE_IBI:
|
||||||
dev = svc_i3c_master_dev_from_addr(master, ibiaddr);
|
dev = svc_i3c_master_dev_from_addr(master, ibiaddr);
|
||||||
if (!dev)
|
if (!dev || !is_events_enabled(master, SVC_I3C_EVENT_IBI))
|
||||||
svc_i3c_master_nack_ibi(master);
|
svc_i3c_master_nack_ibi(master);
|
||||||
else
|
else
|
||||||
svc_i3c_master_handle_ibi(master, dev);
|
svc_i3c_master_handle_ibi(master, dev);
|
||||||
break;
|
break;
|
||||||
case SVC_I3C_MSTATUS_IBITYPE_HOT_JOIN:
|
case SVC_I3C_MSTATUS_IBITYPE_HOT_JOIN:
|
||||||
svc_i3c_master_ack_ibi(master, false);
|
if (is_events_enabled(master, SVC_I3C_EVENT_HOTJOIN))
|
||||||
|
svc_i3c_master_ack_ibi(master, false);
|
||||||
|
else
|
||||||
|
svc_i3c_master_nack_ibi(master);
|
||||||
break;
|
break;
|
||||||
case SVC_I3C_MSTATUS_IBITYPE_MASTER_REQUEST:
|
case SVC_I3C_MSTATUS_IBITYPE_MASTER_REQUEST:
|
||||||
svc_i3c_master_nack_ibi(master);
|
svc_i3c_master_nack_ibi(master);
|
||||||
@@ -472,7 +486,9 @@ static void svc_i3c_master_ibi_work(struct work_struct *work)
|
|||||||
svc_i3c_master_emit_stop(master);
|
svc_i3c_master_emit_stop(master);
|
||||||
break;
|
break;
|
||||||
case SVC_I3C_MSTATUS_IBITYPE_HOT_JOIN:
|
case SVC_I3C_MSTATUS_IBITYPE_HOT_JOIN:
|
||||||
queue_work(master->base.wq, &master->hj_work);
|
svc_i3c_master_emit_stop(master);
|
||||||
|
if (is_events_enabled(master, SVC_I3C_EVENT_HOTJOIN))
|
||||||
|
queue_work(master->base.wq, &master->hj_work);
|
||||||
break;
|
break;
|
||||||
case SVC_I3C_MSTATUS_IBITYPE_MASTER_REQUEST:
|
case SVC_I3C_MSTATUS_IBITYPE_MASTER_REQUEST:
|
||||||
default:
|
default:
|
||||||
@@ -1024,7 +1040,7 @@ static int svc_i3c_master_write(struct svc_i3c_master *master,
|
|||||||
static int svc_i3c_master_xfer(struct svc_i3c_master *master,
|
static int svc_i3c_master_xfer(struct svc_i3c_master *master,
|
||||||
bool rnw, unsigned int xfer_type, u8 addr,
|
bool rnw, unsigned int xfer_type, u8 addr,
|
||||||
u8 *in, const u8 *out, unsigned int xfer_len,
|
u8 *in, const u8 *out, unsigned int xfer_len,
|
||||||
unsigned int *read_len, bool continued)
|
unsigned int *actual_len, bool continued)
|
||||||
{
|
{
|
||||||
u32 reg;
|
u32 reg;
|
||||||
int ret;
|
int ret;
|
||||||
@@ -1037,7 +1053,7 @@ static int svc_i3c_master_xfer(struct svc_i3c_master *master,
|
|||||||
SVC_I3C_MCTRL_IBIRESP_NACK |
|
SVC_I3C_MCTRL_IBIRESP_NACK |
|
||||||
SVC_I3C_MCTRL_DIR(rnw) |
|
SVC_I3C_MCTRL_DIR(rnw) |
|
||||||
SVC_I3C_MCTRL_ADDR(addr) |
|
SVC_I3C_MCTRL_ADDR(addr) |
|
||||||
SVC_I3C_MCTRL_RDTERM(*read_len),
|
SVC_I3C_MCTRL_RDTERM(*actual_len),
|
||||||
master->regs + SVC_I3C_MCTRL);
|
master->regs + SVC_I3C_MCTRL);
|
||||||
|
|
||||||
ret = readl_poll_timeout(master->regs + SVC_I3C_MSTATUS, reg,
|
ret = readl_poll_timeout(master->regs + SVC_I3C_MSTATUS, reg,
|
||||||
@@ -1047,6 +1063,7 @@ static int svc_i3c_master_xfer(struct svc_i3c_master *master,
|
|||||||
|
|
||||||
if (readl(master->regs + SVC_I3C_MERRWARN) & SVC_I3C_MERRWARN_NACK) {
|
if (readl(master->regs + SVC_I3C_MERRWARN) & SVC_I3C_MERRWARN_NACK) {
|
||||||
ret = -ENXIO;
|
ret = -ENXIO;
|
||||||
|
*actual_len = 0;
|
||||||
goto emit_stop;
|
goto emit_stop;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1064,6 +1081,7 @@ static int svc_i3c_master_xfer(struct svc_i3c_master *master,
|
|||||||
*/
|
*/
|
||||||
if (SVC_I3C_MSTATUS_IBIWON(reg)) {
|
if (SVC_I3C_MSTATUS_IBIWON(reg)) {
|
||||||
ret = -ENXIO;
|
ret = -ENXIO;
|
||||||
|
*actual_len = 0;
|
||||||
goto emit_stop;
|
goto emit_stop;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1075,7 +1093,7 @@ static int svc_i3c_master_xfer(struct svc_i3c_master *master,
|
|||||||
goto emit_stop;
|
goto emit_stop;
|
||||||
|
|
||||||
if (rnw)
|
if (rnw)
|
||||||
*read_len = ret;
|
*actual_len = ret;
|
||||||
|
|
||||||
ret = readl_poll_timeout(master->regs + SVC_I3C_MSTATUS, reg,
|
ret = readl_poll_timeout(master->regs + SVC_I3C_MSTATUS, reg,
|
||||||
SVC_I3C_MSTATUS_COMPLETE(reg), 0, 1000);
|
SVC_I3C_MSTATUS_COMPLETE(reg), 0, 1000);
|
||||||
@@ -1157,8 +1175,12 @@ static void svc_i3c_master_start_xfer_locked(struct svc_i3c_master *master)
|
|||||||
|
|
||||||
ret = svc_i3c_master_xfer(master, cmd->rnw, xfer->type,
|
ret = svc_i3c_master_xfer(master, cmd->rnw, xfer->type,
|
||||||
cmd->addr, cmd->in, cmd->out,
|
cmd->addr, cmd->in, cmd->out,
|
||||||
cmd->len, &cmd->read_len,
|
cmd->len, &cmd->actual_len,
|
||||||
cmd->continued);
|
cmd->continued);
|
||||||
|
/* cmd->xfer is NULL if I2C or CCC transfer */
|
||||||
|
if (cmd->xfer)
|
||||||
|
cmd->xfer->actual_len = cmd->actual_len;
|
||||||
|
|
||||||
if (ret)
|
if (ret)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1243,7 +1265,7 @@ static int svc_i3c_master_send_bdcast_ccc_cmd(struct svc_i3c_master *master,
|
|||||||
cmd->in = NULL;
|
cmd->in = NULL;
|
||||||
cmd->out = buf;
|
cmd->out = buf;
|
||||||
cmd->len = xfer_len;
|
cmd->len = xfer_len;
|
||||||
cmd->read_len = 0;
|
cmd->actual_len = 0;
|
||||||
cmd->continued = false;
|
cmd->continued = false;
|
||||||
|
|
||||||
mutex_lock(&master->lock);
|
mutex_lock(&master->lock);
|
||||||
@@ -1263,7 +1285,7 @@ static int svc_i3c_master_send_direct_ccc_cmd(struct svc_i3c_master *master,
|
|||||||
struct i3c_ccc_cmd *ccc)
|
struct i3c_ccc_cmd *ccc)
|
||||||
{
|
{
|
||||||
unsigned int xfer_len = ccc->dests[0].payload.len;
|
unsigned int xfer_len = ccc->dests[0].payload.len;
|
||||||
unsigned int read_len = ccc->rnw ? xfer_len : 0;
|
unsigned int actual_len = ccc->rnw ? xfer_len : 0;
|
||||||
struct svc_i3c_xfer *xfer;
|
struct svc_i3c_xfer *xfer;
|
||||||
struct svc_i3c_cmd *cmd;
|
struct svc_i3c_cmd *cmd;
|
||||||
int ret;
|
int ret;
|
||||||
@@ -1281,7 +1303,7 @@ static int svc_i3c_master_send_direct_ccc_cmd(struct svc_i3c_master *master,
|
|||||||
cmd->in = NULL;
|
cmd->in = NULL;
|
||||||
cmd->out = &ccc->id;
|
cmd->out = &ccc->id;
|
||||||
cmd->len = 1;
|
cmd->len = 1;
|
||||||
cmd->read_len = 0;
|
cmd->actual_len = 0;
|
||||||
cmd->continued = true;
|
cmd->continued = true;
|
||||||
|
|
||||||
/* Directed message */
|
/* Directed message */
|
||||||
@@ -1291,7 +1313,7 @@ static int svc_i3c_master_send_direct_ccc_cmd(struct svc_i3c_master *master,
|
|||||||
cmd->in = ccc->rnw ? ccc->dests[0].payload.data : NULL;
|
cmd->in = ccc->rnw ? ccc->dests[0].payload.data : NULL;
|
||||||
cmd->out = ccc->rnw ? NULL : ccc->dests[0].payload.data,
|
cmd->out = ccc->rnw ? NULL : ccc->dests[0].payload.data,
|
||||||
cmd->len = xfer_len;
|
cmd->len = xfer_len;
|
||||||
cmd->read_len = read_len;
|
cmd->actual_len = actual_len;
|
||||||
cmd->continued = false;
|
cmd->continued = false;
|
||||||
|
|
||||||
mutex_lock(&master->lock);
|
mutex_lock(&master->lock);
|
||||||
@@ -1300,8 +1322,8 @@ static int svc_i3c_master_send_direct_ccc_cmd(struct svc_i3c_master *master,
|
|||||||
svc_i3c_master_dequeue_xfer(master, xfer);
|
svc_i3c_master_dequeue_xfer(master, xfer);
|
||||||
mutex_unlock(&master->lock);
|
mutex_unlock(&master->lock);
|
||||||
|
|
||||||
if (cmd->read_len != xfer_len)
|
if (cmd->actual_len != xfer_len)
|
||||||
ccc->dests[0].payload.len = cmd->read_len;
|
ccc->dests[0].payload.len = cmd->actual_len;
|
||||||
|
|
||||||
ret = xfer->ret;
|
ret = xfer->ret;
|
||||||
svc_i3c_master_free_xfer(xfer);
|
svc_i3c_master_free_xfer(xfer);
|
||||||
@@ -1346,12 +1368,13 @@ static int svc_i3c_master_priv_xfers(struct i3c_dev_desc *dev,
|
|||||||
for (i = 0; i < nxfers; i++) {
|
for (i = 0; i < nxfers; i++) {
|
||||||
struct svc_i3c_cmd *cmd = &xfer->cmds[i];
|
struct svc_i3c_cmd *cmd = &xfer->cmds[i];
|
||||||
|
|
||||||
|
cmd->xfer = &xfers[i];
|
||||||
cmd->addr = master->addrs[data->index];
|
cmd->addr = master->addrs[data->index];
|
||||||
cmd->rnw = xfers[i].rnw;
|
cmd->rnw = xfers[i].rnw;
|
||||||
cmd->in = xfers[i].rnw ? xfers[i].data.in : NULL;
|
cmd->in = xfers[i].rnw ? xfers[i].data.in : NULL;
|
||||||
cmd->out = xfers[i].rnw ? NULL : xfers[i].data.out;
|
cmd->out = xfers[i].rnw ? NULL : xfers[i].data.out;
|
||||||
cmd->len = xfers[i].len;
|
cmd->len = xfers[i].len;
|
||||||
cmd->read_len = xfers[i].rnw ? xfers[i].len : 0;
|
cmd->actual_len = xfers[i].rnw ? xfers[i].len : 0;
|
||||||
cmd->continued = (i + 1) < nxfers;
|
cmd->continued = (i + 1) < nxfers;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1391,7 +1414,7 @@ static int svc_i3c_master_i2c_xfers(struct i2c_dev_desc *dev,
|
|||||||
cmd->in = cmd->rnw ? xfers[i].buf : NULL;
|
cmd->in = cmd->rnw ? xfers[i].buf : NULL;
|
||||||
cmd->out = cmd->rnw ? NULL : xfers[i].buf;
|
cmd->out = cmd->rnw ? NULL : xfers[i].buf;
|
||||||
cmd->len = xfers[i].len;
|
cmd->len = xfers[i].len;
|
||||||
cmd->read_len = cmd->rnw ? xfers[i].len : 0;
|
cmd->actual_len = cmd->rnw ? xfers[i].len : 0;
|
||||||
cmd->continued = (i + 1 < nxfers);
|
cmd->continued = (i + 1 < nxfers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1472,6 +1495,7 @@ static int svc_i3c_master_enable_ibi(struct i3c_dev_desc *dev)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
master->enabled_events |= SVC_I3C_EVENT_IBI;
|
||||||
svc_i3c_master_enable_interrupts(master, SVC_I3C_MINT_SLVSTART);
|
svc_i3c_master_enable_interrupts(master, SVC_I3C_MINT_SLVSTART);
|
||||||
|
|
||||||
return i3c_master_enec_locked(m, dev->info.dyn_addr, I3C_CCC_EVENT_SIR);
|
return i3c_master_enec_locked(m, dev->info.dyn_addr, I3C_CCC_EVENT_SIR);
|
||||||
@@ -1483,7 +1507,9 @@ static int svc_i3c_master_disable_ibi(struct i3c_dev_desc *dev)
|
|||||||
struct svc_i3c_master *master = to_svc_i3c_master(m);
|
struct svc_i3c_master *master = to_svc_i3c_master(m);
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
svc_i3c_master_disable_interrupts(master);
|
master->enabled_events &= ~SVC_I3C_EVENT_IBI;
|
||||||
|
if (!master->enabled_events)
|
||||||
|
svc_i3c_master_disable_interrupts(master);
|
||||||
|
|
||||||
ret = i3c_master_disec_locked(m, dev->info.dyn_addr, I3C_CCC_EVENT_SIR);
|
ret = i3c_master_disec_locked(m, dev->info.dyn_addr, I3C_CCC_EVENT_SIR);
|
||||||
|
|
||||||
@@ -1493,6 +1519,39 @@ static int svc_i3c_master_disable_ibi(struct i3c_dev_desc *dev)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int svc_i3c_master_enable_hotjoin(struct i3c_master_controller *m)
|
||||||
|
{
|
||||||
|
struct svc_i3c_master *master = to_svc_i3c_master(m);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = pm_runtime_resume_and_get(master->dev);
|
||||||
|
if (ret < 0) {
|
||||||
|
dev_err(master->dev, "<%s> Cannot get runtime PM.\n", __func__);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
master->enabled_events |= SVC_I3C_EVENT_HOTJOIN;
|
||||||
|
|
||||||
|
svc_i3c_master_enable_interrupts(master, SVC_I3C_MINT_SLVSTART);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int svc_i3c_master_disable_hotjoin(struct i3c_master_controller *m)
|
||||||
|
{
|
||||||
|
struct svc_i3c_master *master = to_svc_i3c_master(m);
|
||||||
|
|
||||||
|
master->enabled_events &= ~SVC_I3C_EVENT_HOTJOIN;
|
||||||
|
|
||||||
|
if (!master->enabled_events)
|
||||||
|
svc_i3c_master_disable_interrupts(master);
|
||||||
|
|
||||||
|
pm_runtime_mark_last_busy(master->dev);
|
||||||
|
pm_runtime_put_autosuspend(master->dev);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void svc_i3c_master_recycle_ibi_slot(struct i3c_dev_desc *dev,
|
static void svc_i3c_master_recycle_ibi_slot(struct i3c_dev_desc *dev,
|
||||||
struct i3c_ibi_slot *slot)
|
struct i3c_ibi_slot *slot)
|
||||||
{
|
{
|
||||||
@@ -1519,6 +1578,8 @@ static const struct i3c_master_controller_ops svc_i3c_master_ops = {
|
|||||||
.recycle_ibi_slot = svc_i3c_master_recycle_ibi_slot,
|
.recycle_ibi_slot = svc_i3c_master_recycle_ibi_slot,
|
||||||
.enable_ibi = svc_i3c_master_enable_ibi,
|
.enable_ibi = svc_i3c_master_enable_ibi,
|
||||||
.disable_ibi = svc_i3c_master_disable_ibi,
|
.disable_ibi = svc_i3c_master_disable_ibi,
|
||||||
|
.enable_hotjoin = svc_i3c_master_enable_hotjoin,
|
||||||
|
.disable_hotjoin = svc_i3c_master_disable_hotjoin,
|
||||||
};
|
};
|
||||||
|
|
||||||
static int svc_i3c_master_prepare_clks(struct svc_i3c_master *master)
|
static int svc_i3c_master_prepare_clks(struct svc_i3c_master *master)
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ enum i3c_hdr_mode {
|
|||||||
* struct i3c_priv_xfer - I3C SDR private transfer
|
* struct i3c_priv_xfer - I3C SDR private transfer
|
||||||
* @rnw: encodes the transfer direction. true for a read, false for a write
|
* @rnw: encodes the transfer direction. true for a read, false for a write
|
||||||
* @len: transfer length in bytes of the transfer
|
* @len: transfer length in bytes of the transfer
|
||||||
|
* @actual_len: actual length in bytes are transferred by the controller
|
||||||
* @data: input/output buffer
|
* @data: input/output buffer
|
||||||
* @data.in: input buffer. Must point to a DMA-able buffer
|
* @data.in: input buffer. Must point to a DMA-able buffer
|
||||||
* @data.out: output buffer. Must point to a DMA-able buffer
|
* @data.out: output buffer. Must point to a DMA-able buffer
|
||||||
@@ -62,6 +63,7 @@ enum i3c_hdr_mode {
|
|||||||
struct i3c_priv_xfer {
|
struct i3c_priv_xfer {
|
||||||
u8 rnw;
|
u8 rnw;
|
||||||
u16 len;
|
u16 len;
|
||||||
|
u16 actual_len;
|
||||||
union {
|
union {
|
||||||
void *in;
|
void *in;
|
||||||
const void *out;
|
const void *out;
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ struct i2c_dev_boardinfo {
|
|||||||
/**
|
/**
|
||||||
* struct i2c_dev_desc - I2C device descriptor
|
* struct i2c_dev_desc - I2C device descriptor
|
||||||
* @common: common part of the I2C device descriptor
|
* @common: common part of the I2C device descriptor
|
||||||
* @boardinfo: pointer to the boardinfo attached to this I2C device
|
|
||||||
* @dev: I2C device object registered to the I2C framework
|
* @dev: I2C device object registered to the I2C framework
|
||||||
* @addr: I2C device address
|
* @addr: I2C device address
|
||||||
* @lvr: LVR (Legacy Virtual Register) needed by the I3C core to know about
|
* @lvr: LVR (Legacy Virtual Register) needed by the I3C core to know about
|
||||||
@@ -434,6 +433,8 @@ struct i3c_bus {
|
|||||||
* for a future IBI
|
* for a future IBI
|
||||||
* This method is mandatory only if ->request_ibi is not
|
* This method is mandatory only if ->request_ibi is not
|
||||||
* NULL.
|
* NULL.
|
||||||
|
* @enable_hotjoin: enable hot join event detect.
|
||||||
|
* @disable_hotjoin: disable hot join event detect.
|
||||||
*/
|
*/
|
||||||
struct i3c_master_controller_ops {
|
struct i3c_master_controller_ops {
|
||||||
int (*bus_init)(struct i3c_master_controller *master);
|
int (*bus_init)(struct i3c_master_controller *master);
|
||||||
@@ -460,6 +461,8 @@ struct i3c_master_controller_ops {
|
|||||||
int (*disable_ibi)(struct i3c_dev_desc *dev);
|
int (*disable_ibi)(struct i3c_dev_desc *dev);
|
||||||
void (*recycle_ibi_slot)(struct i3c_dev_desc *dev,
|
void (*recycle_ibi_slot)(struct i3c_dev_desc *dev,
|
||||||
struct i3c_ibi_slot *slot);
|
struct i3c_ibi_slot *slot);
|
||||||
|
int (*enable_hotjoin)(struct i3c_master_controller *master);
|
||||||
|
int (*disable_hotjoin)(struct i3c_master_controller *master);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -473,6 +476,7 @@ struct i3c_master_controller_ops {
|
|||||||
* @ops: master operations. See &struct i3c_master_controller_ops
|
* @ops: master operations. See &struct i3c_master_controller_ops
|
||||||
* @secondary: true if the master is a secondary master
|
* @secondary: true if the master is a secondary master
|
||||||
* @init_done: true when the bus initialization is done
|
* @init_done: true when the bus initialization is done
|
||||||
|
* @hotjoin: true if the master support hotjoin
|
||||||
* @boardinfo.i3c: list of I3C boardinfo objects
|
* @boardinfo.i3c: list of I3C boardinfo objects
|
||||||
* @boardinfo.i2c: list of I2C boardinfo objects
|
* @boardinfo.i2c: list of I2C boardinfo objects
|
||||||
* @boardinfo: board-level information attached to devices connected on the bus
|
* @boardinfo: board-level information attached to devices connected on the bus
|
||||||
@@ -495,6 +499,7 @@ struct i3c_master_controller {
|
|||||||
const struct i3c_master_controller_ops *ops;
|
const struct i3c_master_controller_ops *ops;
|
||||||
unsigned int secondary : 1;
|
unsigned int secondary : 1;
|
||||||
unsigned int init_done : 1;
|
unsigned int init_done : 1;
|
||||||
|
unsigned int hotjoin: 1;
|
||||||
struct {
|
struct {
|
||||||
struct list_head i3c;
|
struct list_head i3c;
|
||||||
struct list_head i2c;
|
struct list_head i2c;
|
||||||
@@ -551,6 +556,8 @@ int i3c_master_register(struct i3c_master_controller *master,
|
|||||||
const struct i3c_master_controller_ops *ops,
|
const struct i3c_master_controller_ops *ops,
|
||||||
bool secondary);
|
bool secondary);
|
||||||
void i3c_master_unregister(struct i3c_master_controller *master);
|
void i3c_master_unregister(struct i3c_master_controller *master);
|
||||||
|
int i3c_master_enable_hotjoin(struct i3c_master_controller *master);
|
||||||
|
int i3c_master_disable_hotjoin(struct i3c_master_controller *master);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* i3c_dev_get_master_data() - get master private data attached to an I3C
|
* i3c_dev_get_master_data() - get master private data attached to an I3C
|
||||||
|
|||||||
Reference in New Issue
Block a user