drivers: i3c: introduce I3C API for controllers
This introduces the I3C API for I3C controllers. Currently, this supports one controller per bus under Zephyr. Signed-off-by: Daniel Leung <daniel.leung@intel.com>
This commit is contained in:
parent
9cdd49c6e6
commit
ce7058d2f5
15 changed files with 4350 additions and 4 deletions
15
drivers/i3c/CMakeLists.txt
Normal file
15
drivers/i3c/CMakeLists.txt
Normal file
|
@ -0,0 +1,15 @@
|
|||
# Copyright (c) 2022 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
zephyr_library()
|
||||
|
||||
zephyr_library_sources(
|
||||
i3c_ccc.c
|
||||
i3c_common.c
|
||||
)
|
||||
|
||||
zephyr_library_sources_ifdef(
|
||||
CONFIG_USERSPACE
|
||||
i3c_handlers.c
|
||||
)
|
64
drivers/i3c/Kconfig
Normal file
64
drivers/i3c/Kconfig
Normal file
|
@ -0,0 +1,64 @@
|
|||
# I3C configuration options
|
||||
#
|
||||
# Copyright (c) 2022 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
menuconfig I3C
|
||||
bool "I3C Drivers"
|
||||
help
|
||||
Enable I3C Driver Configuration
|
||||
|
||||
if I3C
|
||||
|
||||
module = I3C
|
||||
module-str = i3c
|
||||
source "subsys/logging/Kconfig.template.log_config"
|
||||
|
||||
config I3C_USE_GROUP_ADDR
|
||||
bool "Use Group Addresses"
|
||||
default y
|
||||
help
|
||||
Enable this to use group addresses if supported
|
||||
by the controllers and target devices.
|
||||
|
||||
Says Y if unsure.
|
||||
|
||||
menuconfig I3C_USE_IBI
|
||||
bool "Use In-Band Interrupt (IBI)"
|
||||
default y
|
||||
help
|
||||
Enable this to use In-Band Interrupt (IBI).
|
||||
|
||||
Says Y if unsure.
|
||||
|
||||
if I3C_USE_IBI
|
||||
|
||||
config I3C_IBI_MAX_PAYLOAD_SIZE
|
||||
int "Maximum IBI Payload Size"
|
||||
default 16
|
||||
help
|
||||
Maxmium IBI payload size.
|
||||
|
||||
endif # I3C_USE_IBI
|
||||
|
||||
comment "Initialization Priority"
|
||||
|
||||
config I3C_CONTROLLER_INIT_PRIORITY
|
||||
int "I3C Controller Init Priority"
|
||||
# Default is just after CONFIG_KERNEL_INIT_PRIORITY_DEVICE
|
||||
default 50
|
||||
help
|
||||
This is for setting up I3C controller device driver instance
|
||||
and also to perform bus initialization (e.g. dynamic address
|
||||
assignment).
|
||||
|
||||
Note that this needs to be done before the device driver
|
||||
instances of the connected I2C and I3C devices start
|
||||
initializing those devices. This is because some devices
|
||||
may not be addressable until addresses are assigned by
|
||||
the controller.
|
||||
|
||||
comment "Device Drivers"
|
||||
|
||||
endif # I3C
|
446
drivers/i3c/i3c_ccc.c
Normal file
446
drivers/i3c/i3c_ccc.c
Normal file
|
@ -0,0 +1,446 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <zephyr/toolchain.h>
|
||||
#include <zephyr/sys/__assert.h>
|
||||
|
||||
#include <zephyr/drivers/i3c.h>
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_DECLARE(i3c, CONFIG_I3C_LOG_LEVEL);
|
||||
|
||||
int i3c_ccc_do_getbcr(struct i3c_device_desc *target,
|
||||
struct i3c_ccc_getbcr *bcr)
|
||||
{
|
||||
struct i3c_ccc_payload ccc_payload;
|
||||
struct i3c_ccc_target_payload ccc_tgt_payload;
|
||||
|
||||
__ASSERT_NO_MSG(target != NULL);
|
||||
__ASSERT_NO_MSG(target->bus != NULL);
|
||||
__ASSERT_NO_MSG(bcr != NULL);
|
||||
|
||||
ccc_tgt_payload.addr = target->dynamic_addr;
|
||||
ccc_tgt_payload.rnw = 1;
|
||||
ccc_tgt_payload.data = &bcr->bcr;
|
||||
ccc_tgt_payload.data_len = sizeof(bcr->bcr);
|
||||
|
||||
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
||||
ccc_payload.ccc.id = I3C_CCC_GETBCR;
|
||||
ccc_payload.targets.payloads = &ccc_tgt_payload;
|
||||
ccc_payload.targets.num_targets = 1;
|
||||
|
||||
return i3c_do_ccc(target->bus, &ccc_payload);
|
||||
}
|
||||
|
||||
int i3c_ccc_do_getdcr(struct i3c_device_desc *target,
|
||||
struct i3c_ccc_getdcr *dcr)
|
||||
{
|
||||
struct i3c_ccc_payload ccc_payload;
|
||||
struct i3c_ccc_target_payload ccc_tgt_payload;
|
||||
|
||||
__ASSERT_NO_MSG(target != NULL);
|
||||
__ASSERT_NO_MSG(target->bus != NULL);
|
||||
__ASSERT_NO_MSG(dcr != NULL);
|
||||
|
||||
ccc_tgt_payload.addr = target->dynamic_addr;
|
||||
ccc_tgt_payload.rnw = 1;
|
||||
ccc_tgt_payload.data = &dcr->dcr;
|
||||
ccc_tgt_payload.data_len = sizeof(dcr->dcr);
|
||||
|
||||
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
||||
ccc_payload.ccc.id = I3C_CCC_GETDCR;
|
||||
ccc_payload.targets.payloads = &ccc_tgt_payload;
|
||||
ccc_payload.targets.num_targets = 1;
|
||||
|
||||
return i3c_do_ccc(target->bus, &ccc_payload);
|
||||
}
|
||||
|
||||
int i3c_ccc_do_getpid(struct i3c_device_desc *target,
|
||||
struct i3c_ccc_getpid *pid)
|
||||
{
|
||||
struct i3c_ccc_payload ccc_payload;
|
||||
struct i3c_ccc_target_payload ccc_tgt_payload;
|
||||
|
||||
__ASSERT_NO_MSG(target != NULL);
|
||||
__ASSERT_NO_MSG(target->bus != NULL);
|
||||
__ASSERT_NO_MSG(pid != NULL);
|
||||
|
||||
ccc_tgt_payload.addr = target->dynamic_addr;
|
||||
ccc_tgt_payload.rnw = 1;
|
||||
ccc_tgt_payload.data = &pid->pid[0];
|
||||
ccc_tgt_payload.data_len = sizeof(pid->pid);
|
||||
|
||||
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
||||
ccc_payload.ccc.id = I3C_CCC_GETPID;
|
||||
ccc_payload.targets.payloads = &ccc_tgt_payload;
|
||||
ccc_payload.targets.num_targets = 1;
|
||||
|
||||
return i3c_do_ccc(target->bus, &ccc_payload);
|
||||
}
|
||||
|
||||
int i3c_ccc_do_rstact_all(const struct device *controller,
|
||||
enum i3c_ccc_rstact_defining_byte action)
|
||||
{
|
||||
struct i3c_ccc_payload ccc_payload;
|
||||
uint8_t def_byte;
|
||||
|
||||
__ASSERT_NO_MSG(controller != NULL);
|
||||
|
||||
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
||||
ccc_payload.ccc.id = I3C_CCC_RSTACT(true);
|
||||
|
||||
def_byte = (uint8_t)action;
|
||||
ccc_payload.ccc.data = &def_byte;
|
||||
ccc_payload.ccc.data_len = 1U;
|
||||
|
||||
return i3c_do_ccc(controller, &ccc_payload);
|
||||
}
|
||||
|
||||
int i3c_ccc_do_rstdaa_all(const struct device *controller)
|
||||
{
|
||||
struct i3c_ccc_payload ccc_payload;
|
||||
|
||||
__ASSERT_NO_MSG(controller != NULL);
|
||||
|
||||
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
||||
ccc_payload.ccc.id = I3C_CCC_RSTDAA;
|
||||
|
||||
return i3c_do_ccc(controller, &ccc_payload);
|
||||
}
|
||||
|
||||
int i3c_ccc_do_setdasa(const struct i3c_device_desc *target)
|
||||
{
|
||||
struct i3c_ccc_payload ccc_payload;
|
||||
struct i3c_ccc_target_payload ccc_tgt_payload;
|
||||
uint8_t dyn_addr;
|
||||
|
||||
__ASSERT_NO_MSG(target != NULL);
|
||||
__ASSERT_NO_MSG(target->bus != NULL);
|
||||
|
||||
if ((target->static_addr == 0U) || (target->dynamic_addr != 0U)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Note that the 7-bit address needs to start at bit 1
|
||||
* (aka left-justified). So shift left by 1;
|
||||
*/
|
||||
dyn_addr = target->static_addr << 1;
|
||||
|
||||
ccc_tgt_payload.addr = target->static_addr;
|
||||
ccc_tgt_payload.rnw = 0;
|
||||
ccc_tgt_payload.data = &dyn_addr;
|
||||
ccc_tgt_payload.data_len = 1;
|
||||
|
||||
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
||||
ccc_payload.ccc.id = I3C_CCC_SETDASA;
|
||||
ccc_payload.targets.payloads = &ccc_tgt_payload;
|
||||
ccc_payload.targets.num_targets = 1;
|
||||
|
||||
return i3c_do_ccc(target->bus, &ccc_payload);
|
||||
}
|
||||
|
||||
int i3c_ccc_do_events_all_set(const struct device *controller,
|
||||
bool enable, struct i3c_ccc_events *events)
|
||||
{
|
||||
struct i3c_ccc_payload ccc_payload;
|
||||
|
||||
__ASSERT_NO_MSG(controller != NULL);
|
||||
|
||||
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
||||
|
||||
ccc_payload.ccc.id = enable ? I3C_CCC_ENEC(true) : I3C_CCC_DISEC(true);
|
||||
|
||||
ccc_payload.ccc.data = &events->events;
|
||||
ccc_payload.ccc.data_len = sizeof(events->events);
|
||||
|
||||
return i3c_do_ccc(controller, &ccc_payload);
|
||||
}
|
||||
|
||||
int i3c_ccc_do_events_set(struct i3c_device_desc *target,
|
||||
bool enable, struct i3c_ccc_events *events)
|
||||
{
|
||||
struct i3c_ccc_payload ccc_payload;
|
||||
struct i3c_ccc_target_payload ccc_tgt_payload;
|
||||
|
||||
__ASSERT_NO_MSG(target != NULL);
|
||||
__ASSERT_NO_MSG(target->bus != NULL);
|
||||
|
||||
if (target->dynamic_addr == 0U) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ccc_tgt_payload.addr = target->dynamic_addr;
|
||||
ccc_tgt_payload.rnw = 0;
|
||||
ccc_tgt_payload.data = &events->events;
|
||||
ccc_tgt_payload.data_len = sizeof(events->events);
|
||||
|
||||
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
||||
ccc_payload.ccc.id = enable ? I3C_CCC_ENEC(false) : I3C_CCC_DISEC(false);
|
||||
ccc_payload.targets.payloads = &ccc_tgt_payload;
|
||||
ccc_payload.targets.num_targets = 1;
|
||||
|
||||
return i3c_do_ccc(target->bus, &ccc_payload);
|
||||
}
|
||||
|
||||
int i3c_ccc_do_setmwl_all(const struct device *controller,
|
||||
const struct i3c_ccc_mwl *mwl)
|
||||
{
|
||||
struct i3c_ccc_payload ccc_payload;
|
||||
uint8_t data[2];
|
||||
|
||||
__ASSERT_NO_MSG(controller != NULL);
|
||||
|
||||
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
||||
|
||||
ccc_payload.ccc.id = I3C_CCC_SETMWL(true);
|
||||
|
||||
ccc_payload.ccc.data = &data[0];
|
||||
ccc_payload.ccc.data_len = sizeof(data);
|
||||
|
||||
/* The actual data is MSB first. So order the data. */
|
||||
data[0] = (uint8_t)((mwl->len & 0xFF00U) >> 8);
|
||||
data[1] = (uint8_t)(mwl->len & 0xFFU);
|
||||
|
||||
return i3c_do_ccc(controller, &ccc_payload);
|
||||
}
|
||||
|
||||
int i3c_ccc_do_setmwl(const struct i3c_device_desc *target,
|
||||
const struct i3c_ccc_mwl *mwl)
|
||||
{
|
||||
struct i3c_ccc_payload ccc_payload;
|
||||
struct i3c_ccc_target_payload ccc_tgt_payload;
|
||||
uint8_t data[2];
|
||||
|
||||
__ASSERT_NO_MSG(target != NULL);
|
||||
__ASSERT_NO_MSG(target->bus != NULL);
|
||||
|
||||
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
||||
|
||||
ccc_tgt_payload.addr = target->static_addr;
|
||||
ccc_tgt_payload.rnw = 0;
|
||||
ccc_tgt_payload.data = &data[0];
|
||||
ccc_tgt_payload.data_len = sizeof(data);
|
||||
|
||||
ccc_payload.ccc.id = I3C_CCC_SETMWL(false);
|
||||
ccc_payload.targets.payloads = &ccc_tgt_payload;
|
||||
ccc_payload.targets.num_targets = 1;
|
||||
|
||||
/* The actual length is MSB first. So order the data. */
|
||||
data[0] = (uint8_t)((mwl->len & 0xFF00U) >> 8);
|
||||
data[1] = (uint8_t)(mwl->len & 0xFFU);
|
||||
|
||||
return i3c_do_ccc(target->bus, &ccc_payload);
|
||||
}
|
||||
|
||||
int i3c_ccc_do_getmwl(const struct i3c_device_desc *target,
|
||||
struct i3c_ccc_mwl *mwl)
|
||||
{
|
||||
struct i3c_ccc_payload ccc_payload;
|
||||
struct i3c_ccc_target_payload ccc_tgt_payload;
|
||||
uint8_t data[2];
|
||||
int ret;
|
||||
|
||||
__ASSERT_NO_MSG(target != NULL);
|
||||
__ASSERT_NO_MSG(target->bus != NULL);
|
||||
__ASSERT_NO_MSG(mwl != NULL);
|
||||
|
||||
ccc_tgt_payload.addr = target->dynamic_addr;
|
||||
ccc_tgt_payload.rnw = 1;
|
||||
ccc_tgt_payload.data = &data[0];
|
||||
ccc_tgt_payload.data_len = sizeof(data);
|
||||
|
||||
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
||||
ccc_payload.ccc.id = I3C_CCC_GETMWL;
|
||||
ccc_payload.targets.payloads = &ccc_tgt_payload;
|
||||
ccc_payload.targets.num_targets = 1;
|
||||
|
||||
ret = i3c_do_ccc(target->bus, &ccc_payload);
|
||||
|
||||
if (ret == 0) {
|
||||
/* The actual length is MSB first. So order the data. */
|
||||
mwl->len = (data[0] << 8) | data[1];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int i3c_ccc_do_setmrl_all(const struct device *controller,
|
||||
const struct i3c_ccc_mrl *mrl,
|
||||
bool has_ibi_size)
|
||||
{
|
||||
struct i3c_ccc_payload ccc_payload;
|
||||
uint8_t data[3];
|
||||
|
||||
__ASSERT_NO_MSG(controller != NULL);
|
||||
|
||||
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
||||
|
||||
ccc_payload.ccc.id = I3C_CCC_SETMRL(true);
|
||||
|
||||
ccc_payload.ccc.data = &data[0];
|
||||
ccc_payload.ccc.data_len = has_ibi_size ? 3 : 2;
|
||||
|
||||
/* The actual length is MSB first. So order the data. */
|
||||
data[0] = (uint8_t)((mrl->len & 0xFF00U) >> 8);
|
||||
data[1] = (uint8_t)(mrl->len & 0xFFU);
|
||||
|
||||
if (has_ibi_size) {
|
||||
data[2] = mrl->ibi_len;
|
||||
}
|
||||
|
||||
return i3c_do_ccc(controller, &ccc_payload);
|
||||
}
|
||||
|
||||
int i3c_ccc_do_setmrl(const struct i3c_device_desc *target,
|
||||
const struct i3c_ccc_mrl *mrl)
|
||||
{
|
||||
struct i3c_ccc_payload ccc_payload;
|
||||
struct i3c_ccc_target_payload ccc_tgt_payload;
|
||||
uint8_t data[3];
|
||||
|
||||
__ASSERT_NO_MSG(target != NULL);
|
||||
__ASSERT_NO_MSG(target->bus != NULL);
|
||||
|
||||
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
||||
|
||||
ccc_tgt_payload.addr = target->static_addr;
|
||||
ccc_tgt_payload.rnw = 0;
|
||||
ccc_tgt_payload.data = &data[0];
|
||||
|
||||
ccc_payload.ccc.id = I3C_CCC_SETMRL(false);
|
||||
ccc_payload.targets.payloads = &ccc_tgt_payload;
|
||||
ccc_payload.targets.num_targets = 1;
|
||||
|
||||
/* The actual length is MSB first. So order the data. */
|
||||
data[0] = (uint8_t)((mrl->len & 0xFF00U) >> 8);
|
||||
data[1] = (uint8_t)(mrl->len & 0xFFU);
|
||||
|
||||
if ((target->bcr & I3C_BCR_IBI_PAYLOAD_HAS_DATA_BYTE)
|
||||
== I3C_BCR_IBI_PAYLOAD_HAS_DATA_BYTE) {
|
||||
ccc_tgt_payload.data_len = 3;
|
||||
|
||||
data[2] = mrl->ibi_len;
|
||||
} else {
|
||||
ccc_tgt_payload.data_len = 2;
|
||||
}
|
||||
|
||||
return i3c_do_ccc(target->bus, &ccc_payload);
|
||||
}
|
||||
|
||||
int i3c_ccc_do_getmrl(const struct i3c_device_desc *target,
|
||||
struct i3c_ccc_mrl *mrl)
|
||||
{
|
||||
struct i3c_ccc_payload ccc_payload;
|
||||
struct i3c_ccc_target_payload ccc_tgt_payload;
|
||||
uint8_t data[3];
|
||||
bool has_ibi_sz;
|
||||
int ret;
|
||||
|
||||
__ASSERT_NO_MSG(target != NULL);
|
||||
__ASSERT_NO_MSG(target->bus != NULL);
|
||||
__ASSERT_NO_MSG(mrl != NULL);
|
||||
|
||||
has_ibi_sz = (target->bcr & I3C_BCR_IBI_PAYLOAD_HAS_DATA_BYTE)
|
||||
== I3C_BCR_IBI_PAYLOAD_HAS_DATA_BYTE;
|
||||
|
||||
ccc_tgt_payload.addr = target->dynamic_addr;
|
||||
ccc_tgt_payload.rnw = 1;
|
||||
ccc_tgt_payload.data = &data[0];
|
||||
ccc_tgt_payload.data_len = has_ibi_sz ? 3 : 2;
|
||||
|
||||
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
||||
ccc_payload.ccc.id = I3C_CCC_GETMRL;
|
||||
ccc_payload.targets.payloads = &ccc_tgt_payload;
|
||||
ccc_payload.targets.num_targets = 1;
|
||||
|
||||
ret = i3c_do_ccc(target->bus, &ccc_payload);
|
||||
|
||||
if (ret == 0) {
|
||||
/* The actual length is MSB first. So order the data. */
|
||||
mrl->len = (data[0] << 8) | data[1];
|
||||
|
||||
if (has_ibi_sz) {
|
||||
mrl->ibi_len = data[2];
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int i3c_ccc_do_getstatus(const struct i3c_device_desc *target,
|
||||
union i3c_ccc_getstatus *status,
|
||||
enum i3c_ccc_getstatus_fmt fmt,
|
||||
enum i3c_ccc_getstatus_defbyte defbyte)
|
||||
{
|
||||
struct i3c_ccc_payload ccc_payload;
|
||||
struct i3c_ccc_target_payload ccc_tgt_payload;
|
||||
uint8_t defining_byte;
|
||||
uint8_t data[2];
|
||||
int ret;
|
||||
|
||||
__ASSERT_NO_MSG(target != NULL);
|
||||
__ASSERT_NO_MSG(target->bus != NULL);
|
||||
__ASSERT_NO_MSG(status != NULL);
|
||||
|
||||
ccc_tgt_payload.addr = target->dynamic_addr;
|
||||
ccc_tgt_payload.rnw = 1;
|
||||
ccc_tgt_payload.data = &data[0];
|
||||
|
||||
if (fmt == GETSTATUS_FORMAT_1) {
|
||||
ccc_tgt_payload.data_len = 2;
|
||||
} else if (fmt == GETSTATUS_FORMAT_2) {
|
||||
switch (defbyte) {
|
||||
case GETSTATUS_FORMAT_2_TGTSTAT:
|
||||
__fallthrough;
|
||||
case GETSTATUS_FORMAT_2_PRECR:
|
||||
ccc_tgt_payload.data_len = 2;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
} else {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
||||
ccc_payload.ccc.id = I3C_CCC_GETSTATUS;
|
||||
ccc_payload.targets.payloads = &ccc_tgt_payload;
|
||||
ccc_payload.targets.num_targets = 1;
|
||||
|
||||
if (fmt == GETSTATUS_FORMAT_2) {
|
||||
defining_byte = (uint8_t)defbyte;
|
||||
|
||||
ccc_payload.ccc.data = &defining_byte;
|
||||
ccc_payload.ccc.data_len = 1;
|
||||
}
|
||||
|
||||
ret = i3c_do_ccc(target->bus, &ccc_payload);
|
||||
|
||||
if (ret == 0) {
|
||||
/* Received data is MSB first. So order the data. */
|
||||
if (fmt == GETSTATUS_FORMAT_1) {
|
||||
status->fmt1.status = (data[0] << 8) | data[1];
|
||||
} else if (fmt == GETSTATUS_FORMAT_2) {
|
||||
switch (defbyte) {
|
||||
case GETSTATUS_FORMAT_2_TGTSTAT:
|
||||
__fallthrough;
|
||||
case GETSTATUS_FORMAT_2_PRECR:
|
||||
status->fmt2.raw_u16 = (data[0] << 8) | data[1];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
544
drivers/i3c/i3c_common.c
Normal file
544
drivers/i3c/i3c_common.c
Normal file
|
@ -0,0 +1,544 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <zephyr/toolchain.h>
|
||||
#include <zephyr/sys/__assert.h>
|
||||
#include <zephyr/sys/slist.h>
|
||||
|
||||
#include <zephyr/drivers/i3c.h>
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(i3c, CONFIG_I3C_LOG_LEVEL);
|
||||
|
||||
void i3c_dump_msgs(const char *name, const struct i3c_msg *msgs,
|
||||
uint8_t num_msgs, struct i3c_device_desc *target)
|
||||
{
|
||||
LOG_DBG("I3C msg: %s, addr=%x", name, target->dynamic_addr);
|
||||
for (unsigned int i = 0; i < num_msgs; i++) {
|
||||
const struct i3c_msg *msg = &msgs[i];
|
||||
|
||||
LOG_DBG(" %c len=%02x: ",
|
||||
msg->flags & I3C_MSG_READ ? 'R' : 'W', msg->len);
|
||||
if (!(msg->flags & I3C_MSG_READ)) {
|
||||
LOG_HEXDUMP_DBG(msg->buf, msg->len, "contents:");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void i3c_addr_slots_set(struct i3c_addr_slots *slots,
|
||||
uint8_t dev_addr,
|
||||
enum i3c_addr_slot_status status)
|
||||
{
|
||||
int bitpos;
|
||||
int idx;
|
||||
|
||||
__ASSERT_NO_MSG(slots != NULL);
|
||||
|
||||
if (dev_addr > I3C_MAX_ADDR) {
|
||||
/* Invalid address. Do nothing. */
|
||||
return;
|
||||
}
|
||||
|
||||
bitpos = dev_addr * 2;
|
||||
idx = bitpos / BITS_PER_LONG;
|
||||
|
||||
slots->slots[idx] &= ~((unsigned long)I3C_ADDR_SLOT_STATUS_MASK <<
|
||||
(bitpos % BITS_PER_LONG));
|
||||
slots->slots[idx] |= status << (bitpos % BITS_PER_LONG);
|
||||
}
|
||||
|
||||
enum i3c_addr_slot_status
|
||||
i3c_addr_slots_status(struct i3c_addr_slots *slots,
|
||||
uint8_t dev_addr)
|
||||
{
|
||||
unsigned long status;
|
||||
int bitpos;
|
||||
int idx;
|
||||
|
||||
__ASSERT_NO_MSG(slots != NULL);
|
||||
|
||||
if (dev_addr > I3C_MAX_ADDR) {
|
||||
/* Invalid address.
|
||||
* Simply says it's reserved so it will not be
|
||||
* used for anything.
|
||||
*/
|
||||
return I3C_ADDR_SLOT_STATUS_RSVD;
|
||||
}
|
||||
|
||||
bitpos = dev_addr * 2;
|
||||
idx = bitpos / BITS_PER_LONG;
|
||||
|
||||
status = slots->slots[idx] >> (bitpos % BITS_PER_LONG);
|
||||
status &= I3C_ADDR_SLOT_STATUS_MASK;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
int i3c_addr_slots_init(struct i3c_addr_slots *slots,
|
||||
const struct i3c_dev_list *dev_list)
|
||||
{
|
||||
int i, ret = 0;
|
||||
struct i3c_device_desc *i3c_dev;
|
||||
struct i3c_i2c_device_desc *i2c_dev;
|
||||
|
||||
__ASSERT_NO_MSG(slots != NULL);
|
||||
|
||||
(void)memset(slots, 0, sizeof(*slots));
|
||||
|
||||
for (i = 0; i <= 7; i++) {
|
||||
/* Addresses 0 to 7 are reserved */
|
||||
i3c_addr_slots_set(slots, i, I3C_ADDR_SLOT_STATUS_RSVD);
|
||||
|
||||
/*
|
||||
* Addresses within a single bit error of broadcast address
|
||||
* are also reserved.
|
||||
*/
|
||||
i3c_addr_slots_set(slots, I3C_BROADCAST_ADDR ^ BIT(i),
|
||||
I3C_ADDR_SLOT_STATUS_RSVD);
|
||||
|
||||
}
|
||||
|
||||
/* The broadcast address is reserved */
|
||||
i3c_addr_slots_set(slots, I3C_BROADCAST_ADDR,
|
||||
I3C_ADDR_SLOT_STATUS_RSVD);
|
||||
|
||||
/*
|
||||
* If there is a static address for the I3C devices, check
|
||||
* if this address is free, and there is no other devices of
|
||||
* the same (pre-assigned) address on the bus.
|
||||
*/
|
||||
for (i = 0; i < dev_list->num_i3c; i++) {
|
||||
i3c_dev = &dev_list->i3c[i];
|
||||
if (i3c_dev->static_addr != 0U) {
|
||||
if (i3c_addr_slots_is_free(slots, i3c_dev->static_addr)) {
|
||||
/*
|
||||
* Mark address slot as I3C device for now to
|
||||
* detect address collisons. This marking may be
|
||||
* released during address assignment.
|
||||
*/
|
||||
i3c_addr_slots_mark_i3c(slots, i3c_dev->static_addr);
|
||||
} else {
|
||||
/* Address slot is not free */
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Mark all I2C addresses.
|
||||
*/
|
||||
for (i = 0; i < dev_list->num_i2c; i++) {
|
||||
i2c_dev = &dev_list->i2c[i];
|
||||
if (i3c_addr_slots_is_free(slots, i2c_dev->addr)) {
|
||||
i3c_addr_slots_mark_i2c(slots, i2c_dev->addr);
|
||||
} else {
|
||||
/* Address slot is not free */
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool i3c_addr_slots_is_free(struct i3c_addr_slots *slots,
|
||||
uint8_t dev_addr)
|
||||
{
|
||||
enum i3c_addr_slot_status status;
|
||||
|
||||
__ASSERT_NO_MSG(slots != NULL);
|
||||
|
||||
status = i3c_addr_slots_status(slots, dev_addr);
|
||||
|
||||
return (status == I3C_ADDR_SLOT_STATUS_FREE);
|
||||
}
|
||||
|
||||
uint8_t i3c_addr_slots_next_free_find(struct i3c_addr_slots *slots)
|
||||
{
|
||||
uint8_t addr;
|
||||
enum i3c_addr_slot_status status;
|
||||
|
||||
/* Addresses 0 to 7 are reserved. So start at 8. */
|
||||
for (addr = 8; addr < I3C_MAX_ADDR; addr++) {
|
||||
status = i3c_addr_slots_status(slots, addr);
|
||||
if (status == I3C_ADDR_SLOT_STATUS_FREE) {
|
||||
return addr;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct i3c_device_desc *i3c_dev_list_find(const struct i3c_dev_list *dev_list,
|
||||
const struct i3c_device_id *id)
|
||||
{
|
||||
int i;
|
||||
struct i3c_device_desc *ret = NULL;
|
||||
|
||||
__ASSERT_NO_MSG(dev_list != NULL);
|
||||
|
||||
for (i = 0; i < dev_list->num_i3c; i++) {
|
||||
struct i3c_device_desc *desc = &dev_list->i3c[i];
|
||||
|
||||
if (desc->pid == id->pid) {
|
||||
ret = desc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct i3c_device_desc *i3c_dev_list_i3c_addr_find(const struct i3c_dev_list *dev_list,
|
||||
uint8_t addr)
|
||||
{
|
||||
int i;
|
||||
struct i3c_device_desc *ret = NULL;
|
||||
|
||||
__ASSERT_NO_MSG(dev_list != NULL);
|
||||
|
||||
for (i = 0; i < dev_list->num_i3c; i++) {
|
||||
struct i3c_device_desc *desc = &dev_list->i3c[i];
|
||||
|
||||
if (desc->dynamic_addr == addr) {
|
||||
ret = desc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct i3c_i2c_device_desc *i3c_dev_list_i2c_addr_find(const struct i3c_dev_list *dev_list,
|
||||
uint16_t addr)
|
||||
{
|
||||
int i;
|
||||
struct i3c_i2c_device_desc *ret = NULL;
|
||||
|
||||
__ASSERT_NO_MSG(dev_list != NULL);
|
||||
|
||||
for (i = 0; i < dev_list->num_i2c; i++) {
|
||||
struct i3c_i2c_device_desc *desc = &dev_list->i2c[i];
|
||||
|
||||
if (desc->addr == addr) {
|
||||
ret = desc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int i3c_dev_list_daa_addr_helper(struct i3c_addr_slots *addr_slots,
|
||||
const struct i3c_dev_list *dev_list,
|
||||
uint64_t pid, bool must_match,
|
||||
bool assigned_okay,
|
||||
struct i3c_device_desc **target,
|
||||
uint8_t *addr)
|
||||
{
|
||||
struct i3c_device_desc *desc;
|
||||
const uint16_t vendor_id = (uint16_t)(pid >> 32);
|
||||
const uint32_t part_no = (uint32_t)(pid & 0xFFFFFFFFU);
|
||||
uint8_t dyn_addr = 0;
|
||||
int ret = 0;
|
||||
const struct i3c_device_id i3c_id = I3C_DEVICE_ID(pid);
|
||||
|
||||
desc = i3c_dev_list_find(dev_list, &i3c_id);
|
||||
if (must_match && (desc == NULL)) {
|
||||
/*
|
||||
* No device descriptor matching incoming PID and
|
||||
* that we want an exact match.
|
||||
*/
|
||||
ret = -ENODEV;
|
||||
|
||||
LOG_DBG("PID 0x%04x%08x is not in registered device list",
|
||||
vendor_id, part_no);
|
||||
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (desc->dynamic_addr != 0U) {
|
||||
if (assigned_okay) {
|
||||
/* Return the already assigned address if desired so. */
|
||||
dyn_addr = desc->dynamic_addr;
|
||||
goto out;
|
||||
} else {
|
||||
/*
|
||||
* Bail If target already has an assigned address.
|
||||
* This is probably due to having the same PIDs for multiple targets
|
||||
* in the device tree.
|
||||
*/
|
||||
LOG_ERR("PID 0x%04x%08x already has "
|
||||
"dynamic address (0x%02x) assigned",
|
||||
vendor_id, part_no, desc->dynamic_addr);
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Use the desired dynamic address as the new dynamic address
|
||||
* if the slot is free.
|
||||
*/
|
||||
if (desc->init_dynamic_addr != 0U) {
|
||||
if (i3c_addr_slots_is_free(addr_slots,
|
||||
desc->init_dynamic_addr)) {
|
||||
dyn_addr = desc->init_dynamic_addr;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Find the next available address.
|
||||
*/
|
||||
dyn_addr = i3c_addr_slots_next_free_find(addr_slots);
|
||||
|
||||
if (dyn_addr == 0U) {
|
||||
/* No free addresses available */
|
||||
LOG_DBG("No more free addresses available.");
|
||||
ret = -ENOSPC;
|
||||
}
|
||||
|
||||
out:
|
||||
*addr = dyn_addr;
|
||||
*target = desc;
|
||||
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
int i3c_device_basic_info_get(struct i3c_device_desc *target)
|
||||
{
|
||||
int ret;
|
||||
uint8_t tmp_bcr;
|
||||
|
||||
struct i3c_ccc_getbcr bcr = {0};
|
||||
struct i3c_ccc_getdcr dcr = {0};
|
||||
struct i3c_ccc_mrl mrl = {0};
|
||||
struct i3c_ccc_mwl mwl = {0};
|
||||
|
||||
/*
|
||||
* Since some CCC functions requires BCR to function
|
||||
* correctly, we save the BCR here and update the BCR
|
||||
* in the descriptor. If any following operations fails,
|
||||
* we can restore the BCR.
|
||||
*/
|
||||
tmp_bcr = target->bcr;
|
||||
|
||||
/* GETBCR */
|
||||
ret = i3c_ccc_do_getbcr(target, &bcr);
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
target->bcr = bcr.bcr;
|
||||
|
||||
/* GETDCR */
|
||||
ret = i3c_ccc_do_getdcr(target, &dcr);
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* GETMRL */
|
||||
ret = i3c_ccc_do_getmrl(target, &mrl);
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* GETMWL */
|
||||
ret = i3c_ccc_do_getmwl(target, &mwl);
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
target->dcr = dcr.dcr;
|
||||
target->data_length.mrl = mrl.len;
|
||||
target->data_length.mwl = mwl.len;
|
||||
target->data_length.max_ibi = mrl.ibi_len;
|
||||
|
||||
out:
|
||||
if (ret != 0) {
|
||||
/* Restore BCR is any CCC fails. */
|
||||
target->bcr = tmp_bcr;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Do SETDASA to set static address as dynamic address.
|
||||
*
|
||||
* @param dev Pointer to the device driver instance.
|
||||
* @param[out] True if DAA is still needed. False if all registered
|
||||
* devices have static addresses.
|
||||
*
|
||||
* @retval 0 if successful.
|
||||
*/
|
||||
static int i3c_bus_setdasa(const struct device *dev,
|
||||
const struct i3c_dev_list *dev_list,
|
||||
bool *need_daa)
|
||||
{
|
||||
int i, ret;
|
||||
|
||||
*need_daa = false;
|
||||
|
||||
/* Loop through the registered I3C devices */
|
||||
for (i = 0; i < dev_list->num_i3c; i++) {
|
||||
struct i3c_device_desc *desc = &dev_list->i3c[i];
|
||||
|
||||
/*
|
||||
* A device without static address => need to do
|
||||
* dynamic address assignment.
|
||||
*/
|
||||
if (desc->static_addr == 0U) {
|
||||
*need_daa = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* If there is a desired dynamic address and it is
|
||||
* not the same as the static address, wait till
|
||||
* ENTDAA to do address assignment as this is
|
||||
* no longer SETDASA.
|
||||
*/
|
||||
if ((desc->init_dynamic_addr != 0U) &&
|
||||
(desc->init_dynamic_addr != desc->static_addr)) {
|
||||
*need_daa = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG_DBG("SETDASA for 0x%x", desc->static_addr);
|
||||
|
||||
ret = i3c_ccc_do_setdasa(desc);
|
||||
if (ret == 0) {
|
||||
desc->dynamic_addr = desc->static_addr;
|
||||
} else {
|
||||
LOG_ERR("SETDASA error on address 0x%x (%d)",
|
||||
desc->static_addr, ret);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int i3c_bus_init(const struct device *dev, const struct i3c_dev_list *dev_list)
|
||||
{
|
||||
int i, ret = 0;
|
||||
bool need_daa = true;
|
||||
struct i3c_ccc_events i3c_events;
|
||||
|
||||
/*
|
||||
* Reset all connected targets. Also reset dynamic
|
||||
* addresses for all devices as we have no idea what
|
||||
* dynamic addresses the connected devices have
|
||||
* (e.g. assigned during previous power cycle).
|
||||
*
|
||||
* Note that we ignore error for both RSTACT and RSTDAA
|
||||
* as there may not be any connected devices responding
|
||||
* to these CCCs.
|
||||
*/
|
||||
if (i3c_ccc_do_rstact_all(dev, I3C_CCC_RSTACT_RESET_WHOLE_TARGET) != 0) {
|
||||
/*
|
||||
* Reset Whole Target support is not required so
|
||||
* if there is any NACK, we want to at least reset
|
||||
* the I3C peripheral of targets.
|
||||
*/
|
||||
LOG_DBG("Broadcast RSTACT (whole target) was NACK.");
|
||||
|
||||
if (i3c_ccc_do_rstact_all(dev, I3C_CCC_RSTACT_PERIPHERAL_ONLY) != 0) {
|
||||
LOG_DBG("Broadcast RSTACT (peripehral) was NACK.");
|
||||
}
|
||||
}
|
||||
|
||||
if (i3c_ccc_do_rstdaa_all(dev) != 0) {
|
||||
LOG_DBG("Broadcast RSTDAA was NACK.");
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable all events from targets to avoid them
|
||||
* interfering with bus initialization,
|
||||
* especially during DAA.
|
||||
*/
|
||||
i3c_events.events = I3C_CCC_EVT_ALL;
|
||||
ret = i3c_ccc_do_events_all_set(dev, false, &i3c_events);
|
||||
if (ret != 0) {
|
||||
LOG_DBG("Broadcast DISEC was NACK.");
|
||||
}
|
||||
|
||||
/*
|
||||
* Set static addresses as dynamic addresses.
|
||||
*/
|
||||
ret = i3c_bus_setdasa(dev, dev_list, &need_daa);
|
||||
if (ret != 0) {
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform Dynamic Address Assignment if needed.
|
||||
*/
|
||||
if (need_daa) {
|
||||
ret = i3c_do_daa(dev);
|
||||
if (ret != 0) {
|
||||
/*
|
||||
* Spec says to try once more
|
||||
* if DAA fails the first time.
|
||||
*/
|
||||
ret = i3c_do_daa(dev);
|
||||
if (ret != 0) {
|
||||
/*
|
||||
* Failure to finish dynamic address assignment
|
||||
* is not the end of world... hopefully.
|
||||
* Continue on so the devices already have
|
||||
* addresses can still function.
|
||||
*/
|
||||
LOG_ERR("DAA was not successful.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Loop through the registered I3C devices to retrieve
|
||||
* basic target information.
|
||||
*/
|
||||
for (i = 0; i < dev_list->num_i3c; i++) {
|
||||
struct i3c_device_desc *desc = &dev_list->i3c[i];
|
||||
|
||||
if (desc->dynamic_addr == 0U) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = i3c_device_basic_info_get(desc);
|
||||
if (ret != 0) {
|
||||
LOG_ERR("Error getting basic device info for 0x%02x",
|
||||
desc->static_addr);
|
||||
} else {
|
||||
LOG_DBG("Target 0x%02x, BCR 0x%02x, DCR 0x%02x, MRL %d, MWL %d, IBI %d",
|
||||
desc->dynamic_addr, desc->bcr, desc->dcr,
|
||||
desc->data_length.mrl, desc->data_length.mwl,
|
||||
desc->data_length.max_ibi);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Only re-enable Hot-Join from targets.
|
||||
* Target interrupts will be enabled when IBI is enabled.
|
||||
* And transferring controller role is not supported so not need to
|
||||
* enable the event.
|
||||
*/
|
||||
i3c_events.events = I3C_CCC_EVT_HJ;
|
||||
ret = i3c_ccc_do_events_all_set(dev, true, &i3c_events);
|
||||
if (ret != 0) {
|
||||
LOG_DBG("Broadcast ENEC was NACK.");
|
||||
}
|
||||
|
||||
err_out:
|
||||
return ret;
|
||||
}
|
82
drivers/i3c/i3c_handlers.c
Normal file
82
drivers/i3c/i3c_handlers.c
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/drivers/i3c.h>
|
||||
#include <string.h>
|
||||
#include <zephyr/syscall_handler.h>
|
||||
|
||||
static inline int z_vrfy_i3c_do_ccc(const struct device *dev,
|
||||
struct i3c_ccc_payload *payload)
|
||||
{
|
||||
Z_OOPS(Z_SYSCALL_DRIVER_I3C(dev, do_ccc));
|
||||
Z_OOPS(Z_SYSCALL_MEMORY_READ(payload, sizeof(*payload)));
|
||||
Z_OOPS(Z_SYSCALL_MEMORY_WRITE(payload, sizeof(*payload)));
|
||||
|
||||
if (payload->ccc.data != NULL) {
|
||||
Z_OOPS(Z_SYSCALL_MEMORY_ARRAY_READ(payload->ccc.data,
|
||||
payload->ccc.data_len,
|
||||
sizeof(*payload->ccc.data)));
|
||||
Z_OOPS(Z_SYSCALL_MEMORY_ARRAY_WRITE(payload->ccc.data,
|
||||
payload->ccc.data_len,
|
||||
sizeof(*payload->ccc.data)));
|
||||
}
|
||||
|
||||
if (payload->targets.payloads != NULL) {
|
||||
Z_OOPS(Z_SYSCALL_MEMORY_ARRAY_READ(payload->targets.payloads,
|
||||
payload->targets.num_targets,
|
||||
sizeof(*payload->targets.payloads)));
|
||||
Z_OOPS(Z_SYSCALL_MEMORY_ARRAY_WRITE(payload->targets.payloads,
|
||||
payload->targets.num_targets,
|
||||
sizeof(*payload->targets.payloads)));
|
||||
}
|
||||
|
||||
return z_impl_i3c_do_ccc(dev, payload);
|
||||
}
|
||||
#include <syscalls/i3c_do_ccc_mrsh.c>
|
||||
|
||||
static uint32_t copy_i3c_msgs_and_transfer(struct i3c_device_desc *target,
|
||||
const struct i3c_msg *msgs,
|
||||
uint8_t num_msgs)
|
||||
{
|
||||
struct i3c_msg copy[num_msgs];
|
||||
uint8_t i;
|
||||
|
||||
/* Use a local copy to avoid switcheroo attacks. */
|
||||
memcpy(copy, msgs, num_msgs * sizeof(*msgs));
|
||||
|
||||
/* Validate the buffers in each message struct. Read options require
|
||||
* that the target buffer be writable
|
||||
*/
|
||||
for (i = 0U; i < num_msgs; i++) {
|
||||
Z_OOPS(Z_SYSCALL_MEMORY(copy[i].buf, copy[i].len,
|
||||
copy[i].flags & I3C_MSG_READ));
|
||||
}
|
||||
|
||||
return z_impl_i3c_transfer(target, copy, num_msgs);
|
||||
}
|
||||
|
||||
static inline int z_vrfy_i3c_transfer(struct i3c_device_desc *target,
|
||||
struct i3c_msg *msgs, uint8_t num_msgs)
|
||||
{
|
||||
Z_OOPS(Z_SYSCALL_MEMORY_READ(target, sizeof(*target)));
|
||||
Z_OOPS(Z_SYSCALL_OBJ(target->bus, K_OBJ_DRIVER_I3C));
|
||||
|
||||
/* copy_msgs_and_transfer() will allocate a copy on the stack using
|
||||
* VLA, so ensure this won't blow the stack. Most functions defined
|
||||
* in i2c.h use only a handful of messages, so up to 32 messages
|
||||
* should be more than sufficient.
|
||||
*/
|
||||
Z_OOPS(Z_SYSCALL_VERIFY(num_msgs >= 1 && num_msgs < 32));
|
||||
|
||||
/* We need to be able to read the overall array of messages */
|
||||
Z_OOPS(Z_SYSCALL_MEMORY_ARRAY_READ(msgs, num_msgs,
|
||||
sizeof(struct i3c_msg)));
|
||||
|
||||
return copy_i3c_msgs_and_transfer((struct i3c_device_desc *)target,
|
||||
(struct i3c_msg *)msgs,
|
||||
(uint8_t)num_msgs);
|
||||
}
|
||||
#include <syscalls/i3c_transfer_mrsh.c>
|
Loading…
Add table
Add a link
Reference in a new issue