From ce7058d2f576a6cb7b31e7ed308c2c17d31d67a5 Mon Sep 17 00:00:00 2001 From: Daniel Leung Date: Mon, 18 Jul 2022 13:21:12 -0700 Subject: [PATCH] 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 --- CODEOWNERS | 4 + MAINTAINERS.yml | 12 + drivers/CMakeLists.txt | 1 + drivers/Kconfig | 2 + drivers/i3c/CMakeLists.txt | 15 + drivers/i3c/Kconfig | 64 + drivers/i3c/i3c_ccc.c | 446 +++++++ drivers/i3c/i3c_common.c | 544 ++++++++ drivers/i3c/i3c_handlers.c | 82 ++ include/zephyr/drivers/i2c.h | 37 +- include/zephyr/drivers/i3c.h | 1573 +++++++++++++++++++++++ include/zephyr/drivers/i3c/addresses.h | 167 +++ include/zephyr/drivers/i3c/ccc.h | 1064 +++++++++++++++ include/zephyr/drivers/i3c/devicetree.h | 236 ++++ include/zephyr/drivers/i3c/ibi.h | 107 ++ 15 files changed, 4350 insertions(+), 4 deletions(-) create mode 100644 drivers/i3c/CMakeLists.txt create mode 100644 drivers/i3c/Kconfig create mode 100644 drivers/i3c/i3c_ccc.c create mode 100644 drivers/i3c/i3c_common.c create mode 100644 drivers/i3c/i3c_handlers.c create mode 100644 include/zephyr/drivers/i3c.h create mode 100644 include/zephyr/drivers/i3c/addresses.h create mode 100644 include/zephyr/drivers/i3c/ccc.h create mode 100644 include/zephyr/drivers/i3c/devicetree.h create mode 100644 include/zephyr/drivers/i3c/ibi.h diff --git a/CODEOWNERS b/CODEOWNERS index ffe913d97e1..40a2bb0f1b8 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -304,6 +304,7 @@ /drivers/i2s/*litex* @mateusz-holenko @kgugala @pgielda /drivers/i2s/i2s_ll_stm32* @avisconti /drivers/i2s/*nrfx* @anangl +/drivers/i3c/ @dcpleung /drivers/ieee802154/ @rlubos @tbursztyka /drivers/ieee802154/*b91* @andy-liu-telink /drivers/ieee802154/ieee802154_nrf5* @jciupis @@ -535,6 +536,7 @@ /dts/bindings/*/vexriscv* @mateusz-holenko @kgugala @pgielda /dts/bindings/*/andes* @cwshu @kevinwang821020 @jimmyzhe /dts/bindings/*/neorv32* @henrikbrixandersen +/dts/bindings/i3c/ @dcpleung /dts/bindings/pm_cpu_ops/* @carlocaione /dts/bindings/ethernet/*gem.yaml @ibirnbaum /dts/posix/ @aescolar @daor-oti @@ -553,6 +555,8 @@ /include/zephyr/drivers/bluetooth/ @alwa-nordic @jhedberg @Vudentz /include/zephyr/drivers/flash.h @nashif @carlescufi @galak @MaureenHelm @nvlsianpu /include/zephyr/drivers/i2c_emul.h @sjg20 +/include/zephyr/drivers/i3c.h @dcpleung +/include/zephyr/drivers/i3c/ @dcpleung /include/zephyr/drivers/led/ht16k33.h @henrikbrixandersen /include/zephyr/drivers/interrupt_controller/ @dcpleung @nashif /include/zephyr/drivers/interrupt_controller/gic.h @stephanosio diff --git a/MAINTAINERS.yml b/MAINTAINERS.yml index 0b434ba0642..125dcc68c95 100644 --- a/MAINTAINERS.yml +++ b/MAINTAINERS.yml @@ -768,6 +768,18 @@ Documentation: labels: - "area: I2S" +"Drivers: I3C": + status: maintained + maintainers: + - dcpleung + files: + - drivers/i3c/ + - dts/bindings/i3c/ + - include/zephyr/drivers/i3c.h + - include/zephyr/drivers/i3c/ + labels: + - "area: I3C" + "Drivers: IEEE 802.15.4": status: maintained maintainers: diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index 3e4f3648648..901f6fc9668 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -20,6 +20,7 @@ add_subdirectory_ifdef(CONFIG_DMA dma) add_subdirectory_ifdef(CONFIG_GPIO gpio) add_subdirectory_ifdef(CONFIG_EC_HOST_CMD_PERIPH ec_host_cmd_periph) add_subdirectory_ifdef(CONFIG_I2C i2c) +add_subdirectory_ifdef(CONFIG_I3C i3c) add_subdirectory_ifdef(CONFIG_I2S i2s) add_subdirectory_ifdef(CONFIG_MDIO mdio) add_subdirectory_ifdef(CONFIG_IEEE802154 ieee802154) diff --git a/drivers/Kconfig b/drivers/Kconfig index 42abc99dae3..adac8c02a31 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -43,6 +43,8 @@ source "drivers/sdhc/Kconfig" source "drivers/i2c/Kconfig" +source "drivers/i3c/Kconfig" + source "drivers/i2s/Kconfig" source "drivers/dai/Kconfig" diff --git a/drivers/i3c/CMakeLists.txt b/drivers/i3c/CMakeLists.txt new file mode 100644 index 00000000000..8a941aef829 --- /dev/null +++ b/drivers/i3c/CMakeLists.txt @@ -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 +) diff --git a/drivers/i3c/Kconfig b/drivers/i3c/Kconfig new file mode 100644 index 00000000000..4d055e7a542 --- /dev/null +++ b/drivers/i3c/Kconfig @@ -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 diff --git a/drivers/i3c/i3c_ccc.c b/drivers/i3c/i3c_ccc.c new file mode 100644 index 00000000000..2aefa47d5fe --- /dev/null +++ b/drivers/i3c/i3c_ccc.c @@ -0,0 +1,446 @@ +/* + * Copyright (c) 2022 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include + +#include + +#include +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; +} diff --git a/drivers/i3c/i3c_common.c b/drivers/i3c/i3c_common.c new file mode 100644 index 00000000000..a00f6b4f5f1 --- /dev/null +++ b/drivers/i3c/i3c_common.c @@ -0,0 +1,544 @@ +/* + * Copyright (c) 2022 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include + +#include + +#include +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; +} diff --git a/drivers/i3c/i3c_handlers.c b/drivers/i3c/i3c_handlers.c new file mode 100644 index 00000000000..e5a5289868d --- /dev/null +++ b/drivers/i3c/i3c_handlers.c @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2022 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +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 + +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 diff --git a/include/zephyr/drivers/i2c.h b/include/zephyr/drivers/i2c.h index 695e1e25cba..7cad3f279b5 100644 --- a/include/zephyr/drivers/i2c.h +++ b/include/zephyr/drivers/i2c.h @@ -75,6 +75,34 @@ struct i2c_dt_spec { uint16_t addr; }; +/** + * @brief Structure initializer for i2c_dt_spec from devicetree (on I3C bus) + * + * This helper macro expands to a static initializer for a struct + * i2c_dt_spec by reading the relevant bus and address data from + * the devicetree. + * + * @param node_id Devicetree node identifier for the I2C device whose + * struct i2c_dt_spec to create an initializer for + */ +#define I2C_DT_SPEC_GET_ON_I3C(node_id) \ + .bus = DEVICE_DT_GET(DT_BUS(node_id)), \ + .addr = DT_PROP_BY_IDX(node_id, reg, 0) + +/** + * @brief Structure initializer for i2c_dt_spec from devicetree (on I2C bus) + * + * This helper macro expands to a static initializer for a struct + * i2c_dt_spec by reading the relevant bus and address data from + * the devicetree. + * + * @param node_id Devicetree node identifier for the I2C device whose + * struct i2c_dt_spec to create an initializer for + */ +#define I2C_DT_SPEC_GET_ON_I2C(node_id) \ + .bus = DEVICE_DT_GET(DT_BUS(node_id)), \ + .addr = DT_REG_ADDR(node_id) + /** * @brief Structure initializer for i2c_dt_spec from devicetree * @@ -85,10 +113,11 @@ struct i2c_dt_spec { * @param node_id Devicetree node identifier for the I2C device whose * struct i2c_dt_spec to create an initializer for */ -#define I2C_DT_SPEC_GET(node_id) \ - { \ - .bus = DEVICE_DT_GET(DT_BUS(node_id)), \ - .addr = DT_REG_ADDR(node_id) \ +#define I2C_DT_SPEC_GET(node_id) \ + { \ + COND_CODE_1(DT_ON_BUS(node_id, i3c), \ + (I2C_DT_SPEC_GET_ON_I3C(node_id)), \ + (I2C_DT_SPEC_GET_ON_I2C(node_id))) \ } /** diff --git a/include/zephyr/drivers/i3c.h b/include/zephyr/drivers/i3c.h new file mode 100644 index 00000000000..503f20a06f4 --- /dev/null +++ b/include/zephyr/drivers/i3c.h @@ -0,0 +1,1573 @@ +/* + * Copyright 2022 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_I3C_H_ +#define ZEPHYR_INCLUDE_DRIVERS_I3C_H_ + +/** + * @brief I3C Interface + * @defgroup i3c_interface I3C Interface + * @ingroup io_interfaces + * @{ + */ + +#include +#include + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Bus Characteristic Register (BCR) + * - BCR[7:6]: Device Role + * - 0: I3C Target + * - 1: I3C Controller capable + * - 2: Reserved + * - 3: Reserved + * - BCR[5]: Advanced Capabilities + * - 0: Does not support optional advanced capabilities. + * - 1: Supports optional advanced capabilities which + * can be viewed via GETCAPS CCC. + * - BCR[4}: Virtual Target Support + * - 0: Is not a virtual target. + * - 1: Is a virtual target. + * - BCR[3]: Offline Capable + * - 0: Will always response to I3C commands. + * - 1: Will not always response to I3C commands. + * - BCR[2]: IBI Payload + * - 0: No data bytes following the accepted IBI. + * - 1: One data byte (MDB, Mandatory Data Byte) follows + * the accepted IBI. Additional data bytes may also + * follows. + * - BCR[1]: IBI Request Capable + * - 0: Not capable + * - 1: Capable + * - BCR[0]: Max Data Speed Limitation + * - 0: No Limitation + * - 1: Limitation obtained via GETMXDS CCC. + */ +#define I3C_BCR_MAX_DATA_SPEED_LIMIT BIT(0) +#define I3C_BCR_IBI_REQUEST_CAPABLE BIT(1) +#define I3C_BCR_IBI_PAYLOAD_HAS_DATA_BYTE BIT(2) +#define I3C_BCR_OFFLINE_CAPABLE BIT(3) +#define I3C_BCR_VIRTUAL_TARGET BIT(4) +#define I3C_BCR_ADV_CAPABILITIES BIT(5) + +#define I3C_BCR_DEVICE_ROLE_I3C_TARGET 0U +#define I3C_BCR_DEVICE_ROLE_I3C_CONTROLLER_CAPABLE 1U + +#define I3C_BCR_DEVICE_ROLE_SHIFT 6U +#define I3C_BCR_DEVICE_ROLE_MASK (0x03U << I3C_BCR_DEVICE_ROLE_SHIFT) + +#define I3C_BCR_DEVICE_ROLE(bcr) \ + (((bcr) & I3C_BCR_DEVICE_ROLE_MASK) >> I3C_BCR_DEVICE_ROLE_SHIFT) + +/* + * Legacy Virtual Register (LVR) + * - LVR[7:5]: I2C device index: + * - 0: I2C device has a 50 ns spike filter where + * it is not affected by high frequency on SCL. + * - 1: I2C device does not have a 50 ns spike filter + * but can work with high frequency on SCL. + * - 2: I2C device does not have a 50 ns spike filter + * and cannot work with high frequency on SCL. + * - LVR[4]: I2C mode indicator: + * - 0: FM+ mode + * - 1: FM mode + * - LVR[3:0]: Reserved. + */ +#define I3C_DCR_I2C_FM_PLUS_MODE 0 +#define I3C_DCR_I2C_FM_MODE 1 + +#define I3C_DCR_I2C_MODE_SHIFT 4 +#define I3C_DCR_I2C_MODE_MASK BIT(4) + +#define I3C_DCR_I2C_MODE(dcr) \ + (((mode) & I3C_DCR_I2C_MODE_MASK) >> I3C_DCR_I2C_MODE_SHIFT) + +#define I3C_DCR_I2C_DEV_IDX_0 0 +#define I3C_DCR_I2C_DEV_IDX_1 1 +#define I3C_DCR_I2C_DEV_IDX_2 2 + +#define I3C_DCR_I2C_DEV_IDX_SHIFT 5 +#define I3C_DCR_I2C_DEV_IDX_MASK (0x07U << I3C_DCR_I2C_DEV_IDX_SHIFT) + +#define I3C_DCR_I2C_DEV_IDX(dcr) \ + (((dcr) & I3C_DCR_I2C_DEV_IDX_MASK) >> I3C_DCR_I2C_DEV_IDX_SHIFT) + +/** + * @brief I3C bus mode + */ +enum i3c_bus_mode { + /** Only I3C devices are on the bus. */ + I3C_BUS_MODE_PURE, + + /** + * Both I3C and legacy I2C devices are on the bus. + * The I2C devices have 50ns spike filter on SCL. + */ + I3C_BUS_MODE_MIXED_FAST, + + /** + * Both I3C and legacy I2C devices are on the bus. + * The I2C devices do not have 50ns spike filter on SCL + * and can tolerate maximum SDR SCL clock frequency. + */ + I3C_BUS_MODE_MIXED_LIMITED, + + /** + * Both I3C and legacy I2C devices are on the bus. + * The I2C devices do not have 50ns spike filter on SCL + * but cannot tolerate maximum SDR SCL clock frequency. + */ + I3C_BUS_MODE_MIXED_SLOW, + + I3C_BUS_MODE_MAX = I3C_BUS_MODE_MIXED_SLOW, + I3C_BUS_MODE_INVALID, +}; + +/** + * @brief I2C bus speed under I3C bus. + * + * Only FM and FM+ modes are supported for I2C devices under I3C bus. + */ +enum i3c_i2c_speed_type { + /** I2C FM mode */ + I3C_I2C_SPEED_FM, + + /** I2C FM+ mode */ + I3C_I2C_SPEED_FMPLUS, + + I3C_I2C_SPEED_MAX = I3C_I2C_SPEED_FMPLUS, + I3C_I2C_SPEED_INVALID, +}; + +/** + * @brief I3C data rate + * + * I3C data transfer rate defined by the I3C specification. + */ +enum i3c_data_rate { + /** Single Data Rate messaging */ + I3C_DATA_RATE_SDR, + + /** High Data Rate - Double Data Rate messaging */ + I3C_DATA_RATE_HDR_DDR, + + /** High Data Rate - Ternary Symbol Legacy-inclusive-Bus */ + I3C_DATA_RATE_HDR_TSL, + + /** High Data Rate - Ternary Symbol for Pure Bus */ + I3C_DATA_RATE_HDR_TSP, + + /** High Data Rate - Bulk Transport */ + I3C_DATA_RATE_HDR_BT, + + I3C_DATA_RATE_MAX = I3C_DATA_RATE_HDR_BT, + I3C_DATA_RATE_INVALID, +}; + +/** + * @brief I3C SDR Controller Error Codes + * + * These are error codes defined by the I3C specification. + * + * @c I3C_ERROR_CE_UNKNOWN and @c I3C_ERROR_CE_NONE are not + * official error codes according to the specification. + * These are there simply to aid in error handling during + * interactions with the I3C drivers and subsystem. + */ +enum i3c_sdr_controller_error_codes { + /** Transaction after sending CCC */ + I3C_ERROR_CE0, + + /** Monitoring Error */ + I3C_ERROR_CE1, + + /** No response to broadcast address (0x7E) */ + I3C_ERROR_CE2, + + /** Failed Controller Handoff */ + I3C_ERROR_CE3, + + /** Unknown error (not official error code) */ + I3C_ERROR_CE_UNKNOWN, + + /** No error (not official error code) */ + I3C_ERROR_CE_NONE, + + I3C_ERROR_CE_MAX = I3C_ERROR_CE_UNKNOWN, + I3C_ERROR_CE_INVALID, +}; + +/** + * @brief I3C SDR Target Error Codes + * + * These are error codes defined by the I3C specification. + * + * @c I3C_ERROR_TE_UNKNOWN and @c I3C_ERROR_TE_NONE are not + * official error codes according to the specification. + * These are there simply to aid in error handling during + * interactions with the I3C drivers and subsystem. + */ +enum i3c_sdr_target_error_codes { + /** + * Invalid Broadcast Address or + * Dynamic Address after DA assignment + */ + I3C_ERROR_TE0, + + /** CCC Code */ + I3C_ERROR_TE1, + + /** Write Data */ + I3C_ERROR_TE2, + + /** Assigned Address during Dynamic Address Arbitration */ + I3C_ERROR_TE3, + + /** 0x7E/R missing after RESTART during Dynamic Address Arbitration */ + I3C_ERROR_TE4, + + /** Transaction after detecting CCC */ + I3C_ERROR_TE5, + + /** Monitoring Error */ + I3C_ERROR_TE6, + + /** Dead Bus Recovery */ + I3C_ERROR_DBR, + + /** Unknown error (not official error code) */ + I3C_ERROR_TE_UNKNOWN, + + /** No error (not official error code) */ + I3C_ERROR_TE_NONE, + + I3C_ERROR_TE_MAX = I3C_ERROR_TE_UNKNOWN, + I3C_ERROR_TE_INVALID, +}; + +/* + * I3C_MSG_* are I3C Message flags. + */ + +/** Write message to I3C bus. */ +#define I3C_MSG_WRITE (0U << 0U) + +/** Read message from I2C bus. */ +#define I3C_MSG_READ BIT(0) + +/** @cond INTERNAL_HIDDEN */ +#define I3C_MSG_RW_MASK BIT(0) +/** @endcond */ + +/** Send STOP after this message. */ +#define I3C_MSG_STOP BIT(1) + +/** + * RESTART I3C transaction for this message. + * + * @note Not all I3C drivers have or require explicit support for this + * feature. Some drivers require this be present on a read message + * that follows a write, or vice-versa. Some drivers will merge + * adjacent fragments into a single transaction using this flag; some + * will not. + */ +#define I3C_MSG_RESTART BIT(2) + +/** Transfer use HDR mode */ +#define I3C_MSG_HDR BIT(3) + +/** I3C HDR Mode 0 */ +#define I3C_MSG_HDR_MODE0 BIT(0) + +/** I3C HDR Mode 1 */ +#define I3C_MSG_HDR_MODE1 BIT(1) + +/** I3C HDR Mode 2 */ +#define I3C_MSG_HDR_MODE2 BIT(2) + +/** I3C HDR Mode 3 */ +#define I3C_MSG_HDR_MODE3 BIT(3) + +/** I3C HDR Mode 4 */ +#define I3C_MSG_HDR_MODE4 BIT(4) + +/** I3C HDR Mode 5 */ +#define I3C_MSG_HDR_MODE5 BIT(5) + +/** I3C HDR Mode 6 */ +#define I3C_MSG_HDR_MODE6 BIT(6) + +/** I3C HDR Mode 7 */ +#define I3C_MSG_HDR_MODE7 BIT(7) + +/** I3C HDR-DDR (Double Data Rate) */ +#define I3C_MSG_HDR_DDR I3C_MSG_HDR_MODE0 + +/** I3C HDR-TSP (Ternary Symbol Pure-bus) */ +#define I3C_MSG_HDR_TSP I3C_MSG_HDR_MODE1 + +/** I3C HDR-TSL (Ternary Symbol Legacy-inclusive-bus) */ +#define I3C_MSG_HDR_TSL I3C_MSG_HDR_MODE2 + +/** I3C HDR-BT (Bulk Transport) */ +#define I3C_MSG_HDR_BT I3C_MSG_HDR_MODE3 + +/** + * @brief One I3C Message. + * + * This defines one I3C message to transact on the I3C bus. + * + * @note Some of the configurations supported by this API may not be + * supported by specific SoC I3C hardware implementations, in + * particular features related to bus transactions intended to read or + * write data from different buffers within a single transaction. + * Invocations of i3c_transfer() may not indicate an error when an + * unsupported configuration is encountered. In some cases drivers + * will generate separate transactions for each message fragment, with + * or without presence of @ref I3C_MSG_RESTART in #flags. + */ +struct i3c_msg { + /** Data buffer in bytes */ + uint8_t *buf; + + /** Length of buffer in bytes */ + uint32_t len; + + /** Flags for this message */ + uint8_t flags; + + /** + * HDR mode (@c I3C_MSG_HDR_MODE*) for transfer + * if any @c I3C_MSG_HDR_* is set in @c flags. + * + * Use SDR mode if none is set. + */ + uint8_t hdr_mode; +}; + +/** + * @brief Type of configuration being passed to configure function. + */ +enum i3c_config_type { + I3C_CONFIG_CONTROLLER, + I3C_CONFIG_CUSTOM, +}; + +/** + * @brief Configuration parameters for I3C hardware to act as controller. + */ +struct i3c_config_controller { + /** + * True if the controller is to be the primary controller + * of the bus. False to be the secondary controller. + */ + bool is_primary; + + struct { + /** SCL frequency (in Hz) for I3C transfers. */ + uint32_t i3c; + + /** SCL frequency (in Hz) for I2C transfers. */ + uint32_t i2c; + } scl; + + /** + * Bit mask of supported HDR modes (0 - 7). + * + * This can be used to enable or disable HDR mode + * supported by the hardware at runtime. + */ + uint8_t supported_hdr; +}; + +/** + * @brief Custom I3C configuration parameters. + * + * This can be used to configure the I3C hardware on parameters + * not covered by @see i3c_config_controller or @see i3c_config_target. + * Mostly used to configure vendor specific parameters of the I3C + * hardware. + */ +struct i3c_config_custom { + /** ID of the configuration parameter. */ + uint32_t id; + + union { + /** Value of configuration parameter. */ + uintptr_t val; + + /** + * Pointer to configuration parameter. + * + * Mainly used to pointer to a struct that + * the device driver understands. + */ + void *ptr; + }; +}; + +/** + * @cond INTERNAL_HIDDEN + * + * These are for internal use only, so skip these in + * public documentation. + */ +struct i3c_device_desc; +struct i3c_device_id; +struct i3c_i2c_device_desc; + +__subsystem struct i3c_driver_api { + /** + * For backward compatibility to I2C API. + * + * @see i2c_driver_api for more information. + * + * (DO NOT MOVE! Must be at the beginning.) + */ + struct i2c_driver_api i2c_api; + + /** + * Configure the I3C hardware. + * + * @see i3c_configure + * + * @param dev Pointer to controller device driver instance. + * @param type Type of configuration parameters being passed + * in @p config. + * @param config Pointer to the configuration parameters. + * + * @return @see i3c_configure + */ + int (*configure)(const struct device *dev, + enum i3c_config_type type, void *config); + + /** + * Get configuration of the I3C hardware. + * + * @see i3c_config_get + * + * @param[in] dev Pointer to controller device driver instance. + * @param[in] type Type of configuration parameters being passed + * in @p config. + * @param[in, out] config Pointer to the configuration parameters. + * + * @return @see i3c_config_get + */ + int (*config_get)(const struct device *dev, + enum i3c_config_type type, void *config); + + /** + * Perform bus recovery + * + * Controller only API. + * + * @see i3c_recover_bus + * + * @param dev Pointer to controller device driver instance. + * + * @return @see i3c_recover_bus + */ + int (*recover_bus)(const struct device *dev); + + /** + * Perform Dynamic Address Assignment via ENTDAA. + * + * Controller only API. + * + * @see i3c_do_daa + * + * @param dev Pointer to controller device driver instance. + * + * @return @see i3c_do_daa + */ + int (*do_daa)(const struct device *dev); + + /** + * Send Common Command Code (CCC). + * + * Controller only API. + * + * @see i3c_do_ccc + * + * @param dev Pointer to controller device driver instance. + * @param payload Pointer to the CCC payload. + * + * @return @see i3c_do_ccc + */ + int (*do_ccc)(const struct device *dev, + struct i3c_ccc_payload *payload); + + /** + * Transfer messages in I3C mode. + * + * @see i3c_transfer + * + * @param dev Pointer to controller device driver instance. + * @param target Pointer to target device descriptor. + * @param msg Pointer to I3C messages. + * @param num_msgs Number of messages to transfer. + * + * @return @see i3c_transfer + */ + int (*i3c_xfers)(const struct device *dev, + struct i3c_device_desc *target, + struct i3c_msg *msgs, + uint8_t num_msgs); + + /** + * Find a registered I3C target device. + * + * Controller only API. + * + * This returns the I3C device descriptor of the I3C device + * matching the incoming @p id. + * + * @param dev Pointer to controller device driver instance. + * @param id Pointer to I3C device ID. + * + * @return @see i3c_device_find. + */ + struct i3c_device_desc *(*i3c_device_find)(const struct device *dev, + const struct i3c_device_id *id); + + /** + * Raise In-Band Interrupt (IBI). + * + * Target device only API. + * + * @see i3c_ibi_request + * + * @param dev Pointer to controller device driver instance. + * @param request Pointer to IBI request struct. + * + * @return @see i3c_ibi_request + */ + int (*ibi_raise)(const struct device *dev, + struct i3c_ibi *request); + + /** + * Enable receiving IBI from a target. + * + * Controller only API. + * + * @see i3c_ibi_enable + * + * @param dev Pointer to controller device driver instance. + * @param target Pointer to target device descriptor. + * + * @return @see i3c_ibi_enable + */ + int (*ibi_enable)(const struct device *dev, + struct i3c_device_desc *target); + + /** + * Disable receiving IBI from a target. + * + * Controller only API. + * + * @see i3c_ibi_disable + * + * @param dev Pointer to controller device driver instance. + * @param target Pointer to target device descriptor. + * + * @return @see i3c_ibi_disable + */ + int (*ibi_disable)(const struct device *dev, + struct i3c_device_desc *target); +}; + +/** + * @endcond + */ + +/** + * @brief Structure used for matching I3C devices. + */ +struct i3c_device_id { + /** Device Provisioned ID */ + const uint64_t pid:48; +}; + +/** + * @brief Structure initializer for i3c_device_id from PID + * + * This helper macro expands to a static initializer for a struct + * i3c_device_id by populating the PID (Provisioned ID) field. + * + * @param pid Provisioned ID. + */ +#define I3C_DEVICE_ID(pid) \ + { \ + .pid = pid \ + } + +/** + * @brief Structure describing a I3C target device. + * + * Instances of this are passed to the I3C controller device APIs, + * for example: + * - i3c_device_register() to tell the controller of a target device. + * - i3c_transfers() to initiate data transfers between controller and + * target device. + * + * Fields @c bus, @c pid and @c static_addr must be initialized by + * the module that implements the target device behavior prior to + * passing the object reference to I3C controller device APIs. + * @c static_addr can be zero if target device does not have static + * address. + * + * Field @c node should not be initialized or modified manually. + */ +struct i3c_device_desc { + /** Private, do not modify */ + sys_snode_t node; + + /** I3C bus to which this target device is attached */ + const struct device * const bus; + + /** Device driver instance of the I3C device */ + const struct device * const dev; + + /** Device Provisioned ID */ + const uint64_t pid:48; + + /** + * Static address for this target device. + * + * 0 if static address is not being used, and only dynamic + * address is used. This means that the target device must + * go through ENTDAA (Dynamic Address Assignment) to get + * a dynamic address before it can communicate with + * the controller. This means SETAASA and SETDASA CCC + * cannot be used to set dynamic address on the target + * device (as both are to tell target device to use static + * address as dynamic address). + */ + const uint8_t static_addr; + + /** + * Initial dynamic address. + * + * This is specified in the device tree property "assigned-address" + * to indicate the desired dynamic address during address + * assignment (SETDASA and ENTDAA). + * + * 0 if there is no preference. + */ + const uint8_t init_dynamic_addr; + + /** + * Dynamic Address for this target device used for communication. + * + * This is to be set by the controller driver in one of + * the following situations: + * - During Dynamic Address Assignment (during ENTDAA) + * - Reset Dynamic Address Assignment (RSTDAA) + * - Set All Addresses to Static Addresses (SETAASA) + * - Set New Dynamic Address (SETNEWDA) + * - Set Dynamic Address from Static Address (SETDASA) + * + * 0 if address has not been assigned. + */ + uint8_t dynamic_addr; + +#if defined(CONFIG_I3C_USE_GROUP_ADDR) || defined(__DOXYGEN__) + /** + * Group address for this target device. Set during: + * - Reset Group Address(es) (RSTGRPA) + * - Set Group Address (SETGRPA) + * + * 0 if group address has not been assigned. + */ + uint8_t group_addr; +#endif /* CONFIG_I3C_USE_GROUP_ADDR */ + + /** + * Bus Characteristic Register (BCR) + * - BCR[7:6]: Device Role + * - 0: I3C Target + * - 1: I3C Controller capable + * - 2: Reserved + * - 3: Reserved + * - BCR[5]: Advanced Capabilities + * - 0: Does not support optional advanced capabilities. + * - 1: Supports optional advanced capabilities which + * can be viewed via GETCAPS CCC. + * - BCR[4}: Virtual Target Support + * - 0: Is not a virtual target. + * - 1: Is a virtual target. + * - BCR[3]: Offline Capable + * - 0: Will always response to I3C commands. + * - 1: Will not always response to I3C commands. + * - BCR[2]: IBI Payload + * - 0: No data bytes following the accepted IBI. + * - 1: One data byte (MDB, Mandatory Data Byte) follows + * the accepted IBI. Additional data bytes may also + * follows. + * - BCR[1]: IBI Request Capable + * - 0: Not capable + * - 1: Capable + * - BCR[0]: Max Data Speed Limitation + * - 0: No Limitation + * - 1: Limitation obtained via GETMXDS CCC. + */ + uint8_t bcr; + + /** + * Device Characteristic Register (DCR) + * + * Describes the type of device. Refer to official documentation + * on what this number means. + */ + uint8_t dcr; + + struct { + /** Maximum Read Speed */ + uint8_t maxrd; + + /** Maximum Write Speed */ + uint8_t maxwr; + + /** Maximum Read turnaround time in microseconds. */ + uint32_t max_read_turnaround; + } data_speed; + + struct { + /** Maximum Read Length */ + uint16_t mrl; + + /** Maximum Write Length */ + uint16_t mwl; + + /** Maximum IBI Payload Size. Valid only if BCR[2] is 1. */ + uint8_t max_ibi; + } data_length; + + /** Private data by the controller to aid in transactions. Do not modify. */ + void *controller_priv; + +#if defined(CONFIG_I3C_USE_IBI) || defined(__DOXYGEN__) + /** + * In-Band Interrupt (IBI) callback. + */ + i3c_target_ibi_cb_t ibi_cb; +#endif /* CONFIG_I3C_USE_IBI */ +}; + +/** + * @brief Structure describing a I2C device on I3C bus. + * + * Instances of this are passed to the I3C controller device APIs, + * for example: + * () i3c_i2c_device_register() to tell the controller of an I2C device. + * () i3c_i2c_transfers() to initiate data transfers between controller + * and I2C device. + * + * Fields other than @c node must be initialized by the module that + * implements the device behavior prior to passing the object + * reference to I3C controller device APIs. + */ +struct i3c_i2c_device_desc { + /** Private, do not modify */ + sys_snode_t node; + + /** I3C bus to which this I2C device is attached */ + const struct device *bus; + + /** Static address for this I2C device. */ + const uint16_t addr; + + /** + * Legacy Virtual Register (LVR) + * - LVR[7:5]: I2C device index: + * - 0: I2C device has a 50 ns spike filter where + * it is not affected by high frequency on SCL. + * - 1: I2C device does not have a 50 ns spike filter + * but can work with high frequency on SCL. + * - 2: I2C device does not have a 50 ns spike filter + * and cannot work with high frequency on SCL. + * - LVR[4]: I2C mode indicator: + * - 0: FM+ mode + * - 1: FM mode + * - LVR[3:0]: Reserved. + */ + const uint8_t lvr; + + /** Private data by the controller to aid in transactions. Do not modify. */ + void *controller_priv; +}; + +/** + * @brief Structure for describing attached devices for a controller. + * + * This contains arrays of attached I3C and I2C devices. + * + * This is a helper struct that can be used by controller device + * driver to aid in device management. + */ +struct i3c_dev_list { + /** + * Pointer to array of attached I3C devices. + */ + struct i3c_device_desc * const i3c; + + /** + * Pointer to array of attached I2C devices. + */ + struct i3c_i2c_device_desc * const i2c; + + /** + * Number of I3C devices in array. + */ + const uint8_t num_i3c; + + /** + * Number of I2C devices in array. + */ + const uint8_t num_i2c; +}; + +/** + * @brief Find a I3C target device descriptor by ID. + * + * This finds the I3C target device descriptor in the device list + * matching the provided ID struct (@p id). + * + * @param dev_list Pointer to the device list struct. + * @param id Pointer to I3C device ID struct. + * + * @return Pointer the the I3C target device descriptor, or + * NULL if none is found. + */ +struct i3c_device_desc *i3c_dev_list_find(const struct i3c_dev_list *dev_list, + const struct i3c_device_id *id); + +/** + * @brief Find a I3C target device descriptor by dynamic address. + * + * This finds the I3C target device descriptor in the device list + * matching the dynamic address (@p addr) + * + * @param dev_list Pointer to the device list struct. + * @param addr Dynamic address to be matched. + * + * @return Pointer the the I3C target device descriptor, or + * NULL if none is found. + */ +struct i3c_device_desc *i3c_dev_list_i3c_addr_find(const struct i3c_dev_list *dev_list, + uint8_t addr); + +/** + * @brief Find a I2C target device descriptor by address. + * + * This finds the I2C target device descriptor in the device list + * matching the address (@p addr) + * + * @param dev_list Pointer to the device list struct. + * @param addr Address to be matched. + * + * @return Pointer the the I2C target device descriptor, or + * NULL if none is found. + */ +struct i3c_i2c_device_desc *i3c_dev_list_i2c_addr_find(const struct i3c_dev_list *dev_list, + uint16_t addr); + +/** + * @brief Helper function to find a usable address during ENTDAA. + * + * This is a helper function to find a usable address during + * Dynamic Address Assignment. Given the PID (@p pid), it will + * search through the device list for the matching device + * descriptor. If the device descriptor indicates that there is + * a preferred address (i.e. assigned-address in device tree, + * @see i3c_device_desc::init_dynamic_addr), this preferred + * address will be returned if this address is still available. + * If it is not available, another free address will be returned. + * + * If @p must_match is true, the PID (@p pid) must match + * one of the device in the device list. + * + * If @p must_match is false, this will return an arbitrary + * address. This is useful when not all devices are described in + * device tree. Or else, the DAA process cannot proceed since + * there is no address to be assigned. + * + * If @p assigned_okay is true, it will return the same address + * already assigned to the device + * (@see i3c_device_desc::dynamic_addr). If no address has been + * assigned, it behaves as if @p assigned_okay is false. + * This is useful for assigning the same address to the same + * device (for example, hot-join after device coming back from + * suspend). + * + * If @p assigned_okay is false, the device cannot have an address + * assigned already (that @see i3c_device_desc::dynamic_addr is not + * zero). This is mainly used during the initial DAA. + * + * @param[in] addr_slots Pointer to address slots struct. + * @param[in] dev_list Pointer to the device list struct. + * @param[in] pid Provisioned ID of device to be assigned address. + * @param[in] must_match True if PID must match devices in + * the device list. False otherwise. + * @param[in] assigned_okay True if it is okay to return the + * address already assigned to the target + * matching the PID (@p pid). + * @param[out] target Store the pointer of the device descriptor + * if it matches the incoming PID (@p pid). + * @param[out] addr Address to be assigned to target device. + * + * @retval 0 if successful. + * @retval -ENODEV if no device matches the PID (@p pid) in + * the device list and @p must_match is true. + * @retval -EINVAL if the device matching PID (@p pid) already + * has an address assigned or invalid function + * arguments. + */ +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); + +/** + * @brief Configure the I3C hardware. + * + * @param dev Pointer to controller device driver instance. + * @param type Type of configuration parameters being passed + * in @p config. + * @param config Pointer to the configuration parameters. + * + * @retval 0 If successful. + * @retval -EINVAL If invalid configure parameters. + * @retval -EIO General Input/Output errors. + * @retval -ENOSYS If not implemented. + */ +static inline int i3c_configure(const struct device *dev, + enum i3c_config_type type, void *config) +{ + const struct i3c_driver_api *api = + (const struct i3c_driver_api *)dev->api; + + if (api->configure == NULL) { + return -ENOSYS; + } + + return api->configure(dev, type, config); +} + +/** + * @brief Get configuration of the I3C hardware. + * + * This provides a way to get the current configuration of the I3C hardware. + * + * This can return cached config or probed hardware parameters, but it has to + * be up to date with current configuration. + * + * @param[in] dev Pointer to controller device driver instance. + * @param[in] type Type of configuration parameters being passed + * in @p config. + * @param[in,out] config Pointer to the configuration parameters. + * + * Note that if @p type is @c I3C_CONFIG_CUSTOM, @p config must contain + * the ID of the parameter to be retrieved. + * + * @retval 0 If successful. + * @retval -EIO General Input/Output errors. + * @retval -ENOSYS If not implemented. + */ +static inline int i3c_config_get(const struct device *dev, + enum i3c_config_type type, void *config) +{ + const struct i3c_driver_api *api = + (const struct i3c_driver_api *)dev->api; + + if (api->config_get == NULL) { + return -ENOSYS; + } + + return api->config_get(dev, type, config); +} + +/** + * @brief Attempt bus recovery on the I3C bus. + * + * This routine asks the controller to attempt bus recovery. + * + * @retval 0 If successful. + * @retval -EBUSY If bus recovery fails. + * @retval -EIO General input / output error. + * @retval -ENOSYS Bus recovery is not supported by the controller driver. + */ +static inline int i3c_recover_bus(const struct device *dev) +{ + const struct i3c_driver_api *api = + (const struct i3c_driver_api *)dev->api; + + if (api->recover_bus == NULL) { + return -ENOSYS; + } + + return api->recover_bus(dev); +} + +/** + * @brief Perform Dynamic Address Assignment on the I3C bus. + * + * This routine asks the controller to perform dynamic address assignment + * where the controller belongs. Only the active controller of the bus + * should do this. + * + * @note For controller driver implementation, the controller should perform + * SETDASA to allow static addresses to be the dynamic addresses before + * actually doing ENTDAA. + * + * @param dev Pointer to the device structure for the controller driver + * instance. + * + * @retval 0 If successful. + * @retval -EBUSY Bus is busy. + * @retval -EIO General input / output error. + * @retval -ENODEV If a provisioned ID does not match to any target devices + * in the registered device list. + * @retval -ENOSPC No more free addresses can be assigned to target. + * @retval -ENOSYS Dynamic address assignment is not supported by + * the controller driver. + */ +static inline int i3c_do_daa(const struct device *dev) +{ + const struct i3c_driver_api *api = + (const struct i3c_driver_api *)dev->api; + + if (api->do_daa == NULL) { + return -ENOSYS; + } + + return api->do_daa(dev); +} + +/** + * @brief Send CCC to the bus. + * + * @param dev Pointer to the device structure for the controller driver + * instance. + * @param payload Pointer to the structure describing the CCC payload. + * + * @retval 0 If successful. + * @retval -EBUSY Bus is busy. + * @retval -EIO General Input / output error. + * @retval -EINVAL Invalid valid set in the payload structure. + * @retval -ENOSYS Not implemented. + */ +__syscall int i3c_do_ccc(const struct device *dev, + struct i3c_ccc_payload *payload); + +static inline int z_impl_i3c_do_ccc(const struct device *dev, + struct i3c_ccc_payload *payload) +{ + const struct i3c_driver_api *api = + (const struct i3c_driver_api *)dev->api; + + if (api->do_ccc == NULL) { + return -ENOSYS; + } + + return api->do_ccc(dev, payload); +} + +/** + * @brief Perform data transfer from the controller to a I3C target device. + * + * This routine provides a generic interface to perform data transfer + * to a target device synchronously. Use i3c_read()/i3c_write() + * for simple read or write. + * + * The array of message @a msgs must not be NULL. The number of + * message @a num_msgs may be zero, in which case no transfer occurs. + * + * @note Not all scatter/gather transactions can be supported by all + * drivers. As an example, a gather write (multiple consecutive + * `i3c_msg` buffers all configured for `I3C_MSG_WRITE`) may be packed + * into a single transaction by some drivers, but others may emit each + * fragment as a distinct write transaction, which will not produce + * the same behavior. See the documentation of `struct i3c_msg` for + * limitations on support for multi-message bus transactions. + * + * @param target I3C target device descriptor. + * @param msgs Array of messages to transfer. + * @param num_msgs Number of messages to transfer. + * + * @retval 0 If successful. + * @retval -EBUSY Bus is busy. + * @retval -EIO General input / output error. + */ +__syscall int i3c_transfer(struct i3c_device_desc *target, + struct i3c_msg *msgs, uint8_t num_msgs); + +static inline int z_impl_i3c_transfer(struct i3c_device_desc *target, + struct i3c_msg *msgs, uint8_t num_msgs) +{ + const struct i3c_driver_api *api = + (const struct i3c_driver_api *)target->bus->api; + + return api->i3c_xfers(target->bus, target, msgs, num_msgs); +} + +/** + * Find a registered I3C target device. + * + * Controller only API. + * + * This returns the I3C device descriptor of the I3C device + * matching the incoming @p id. + * + * @param dev Pointer to controller device driver instance. + * @param id Pointer to I3C device ID. + * + * @return Pointer to I3C device descriptor, or NULL if + * no I3C device found matching incoming @p id. + */ +static inline +struct i3c_device_desc *i3c_device_find(const struct device *dev, + const struct i3c_device_id *id) +{ + const struct i3c_driver_api *api = + (const struct i3c_driver_api *)dev->api; + + if (api->i3c_device_find == NULL) { + return NULL; + } + + return api->i3c_device_find(dev, id); +} + +/** + * @brief Raise an In-Band Interrupt (IBI). + * + * This raises an In-Band Interrupt (IBI) to the active controller. + * + * @param dev Pointer to controller device driver instance. + * @param request Pointer to the IBI request struct. + * + * @retval 0 if operation is successful. + * @retval -EIO General input / output error. + */ +static inline int i3c_ibi_raise(const struct device *dev, + struct i3c_ibi *request) +{ + const struct i3c_driver_api *api = + (const struct i3c_driver_api *)dev->api; + + if (api->ibi_raise == NULL) { + return -ENOSYS; + } + + return api->ibi_raise(dev, request); +} + +/** + * @brief Enable IBI of a target device. + * + * This enables IBI of a target device where the IBI has already been + * request. + * + * @param target I3C target device descriptor. + * + * @retval 0 If successful. + * @retval -EIO General Input / output error. + * @retval -ENOMEM If these is no more empty entries in + * the controller's IBI table (if the controller + * uses such table). + */ +static inline int i3c_ibi_enable(struct i3c_device_desc *target) +{ + const struct i3c_driver_api *api = + (const struct i3c_driver_api *)target->bus->api; + + if (api->ibi_enable == NULL) { + return -ENOSYS; + } + + return api->ibi_enable(target->bus, target); +} + +/** + * @brief Disable IBI of a target device. + * + * This enables IBI of a target device where the IBI has already been + * request. + * + * @param target I3C target device descriptor. + * + * @retval 0 If successful. + * @retval -EIO General Input / output error. + * @retval -ENODEV If IBI is not previously enabled for @p target. + */ +static inline int i3c_ibi_disable(struct i3c_device_desc *target) +{ + const struct i3c_driver_api *api = + (const struct i3c_driver_api *)target->bus->api; + + if (api->ibi_disable == NULL) { + return -ENOSYS; + } + + return api->ibi_disable(target->bus, target); +} + +/** + * @brief Check if target's IBI has payload. + * + * This reads the BCR from the device descriptor struct to determine + * whether IBI from device has payload. + * + * Note that BCR must have been obtained from device and + * @see i3c_device_desc::bcr must be set. + * + * @return True if IBI has payload, false otherwise. + */ +static inline int i3c_ibi_has_payload(struct i3c_device_desc *target) +{ + return (target->bcr & I3C_BCR_IBI_PAYLOAD_HAS_DATA_BYTE) + == I3C_BCR_IBI_PAYLOAD_HAS_DATA_BYTE; +} + +/** + * @brief Check if device is IBI capable. + * + * This reads the BCR from the device descriptor struct to determine + * whether device is capable of IBI. + * + * Note that BCR must have been obtained from device and + * @see i3c_device_desc::bcr must be set. + * + * @return True if IBI has payload, false otherwise. + */ +static inline int i3c_device_is_ibi_capable(struct i3c_device_desc *target) +{ + return (target->bcr & I3C_BCR_IBI_REQUEST_CAPABLE) + == I3C_BCR_IBI_REQUEST_CAPABLE; +} + +/** + * @brief Write a set amount of data to an I3C target device. + * + * This routine writes a set amount of data synchronously. + * + * @param target I3C target device descriptor. + * @param buf Memory pool from which the data is transferred. + * @param num_bytes Number of bytes to write. + * + * @retval 0 If successful. + * @retval -EBUSY Bus is busy. + * @retval -EIO General input / output error. + */ +static inline int i3c_write(struct i3c_device_desc *target, + const uint8_t *buf, uint32_t num_bytes) +{ + struct i3c_msg msg; + + msg.buf = (uint8_t *)buf; + msg.len = num_bytes; + msg.flags = I3C_MSG_WRITE | I3C_MSG_STOP; + + return i3c_transfer(target, &msg, 1); +} + +/** + * @brief Read a set amount of data from an I3C target device. + * + * This routine reads a set amount of data synchronously. + * + * @param target I3C target device descriptor. + * @param buf Memory pool that stores the retrieved data. + * @param num_bytes Number of bytes to read. + * + * @retval 0 If successful. + * @retval -EBUSY Bus is busy. + * @retval -EIO General input / output error. + */ +static inline int i3c_read(struct i3c_device_desc *target, + uint8_t *buf, uint32_t num_bytes) +{ + struct i3c_msg msg; + + msg.buf = buf; + msg.len = num_bytes; + msg.flags = I3C_MSG_READ | I3C_MSG_STOP; + + return i3c_transfer(target, &msg, 1); +} + +/** + * @brief Write then read data from an I3C target device. + * + * This supports the common operation "this is what I want", "now give + * it to me" transaction pair through a combined write-then-read bus + * transaction. + * + * @param target I3C target device descriptor. + * @param write_buf Pointer to the data to be written + * @param num_write Number of bytes to write + * @param read_buf Pointer to storage for read data + * @param num_read Number of bytes to read + * + * @retval 0 if successful + * @retval -EBUSY Bus is busy. + * @retval -EIO General input / output error. + */ +static inline int i3c_write_read(struct i3c_device_desc *target, + const void *write_buf, size_t num_write, + void *read_buf, size_t num_read) +{ + struct i3c_msg msg[2]; + + msg[0].buf = (uint8_t *)write_buf; + msg[0].len = num_write; + msg[0].flags = I3C_MSG_WRITE; + + msg[1].buf = (uint8_t *)read_buf; + msg[1].len = num_read; + msg[1].flags = I3C_MSG_RESTART | I3C_MSG_READ | I3C_MSG_STOP; + + return i3c_transfer(target, msg, 2); +} + +/** + * @brief Read multiple bytes from an internal address of an I3C target device. + * + * This routine reads multiple bytes from an internal address of an + * I3C target device synchronously. + * + * Instances of this may be replaced by i3c_write_read(). + * + * @param target I3C target device descriptor, + * @param start_addr Internal address from which the data is being read. + * @param buf Memory pool that stores the retrieved data. + * @param num_bytes Number of bytes being read. + * + * @retval 0 If successful. + * @retval -EBUSY Bus is busy. + * @retval -EIO General input / output error. + */ +static inline int i3c_burst_read(struct i3c_device_desc *target, + uint8_t start_addr, + uint8_t *buf, + uint32_t num_bytes) +{ + return i3c_write_read(target, + &start_addr, sizeof(start_addr), + buf, num_bytes); +} + +/** + * @brief Write multiple bytes to an internal address of an I3C target device. + * + * This routine writes multiple bytes to an internal address of an + * I3C target device synchronously. + * + * @warning The combined write synthesized by this API may not be + * supported on all I3C devices. Uses of this API may be made more + * portable by replacing them with calls to i3c_write() passing a + * buffer containing the combined address and data. + * + * @param target I3C target device descriptor. + * @param start_addr Internal address to which the data is being written. + * @param buf Memory pool from which the data is transferred. + * @param num_bytes Number of bytes being written. + * + * @retval 0 If successful. + * @retval -EBUSY Bus is busy. + * @retval -EIO General input / output error. + */ +static inline int i3c_burst_write(struct i3c_device_desc *target, + uint8_t start_addr, + const uint8_t *buf, + uint32_t num_bytes) +{ + struct i3c_msg msg[2]; + + msg[0].buf = &start_addr; + msg[0].len = 1U; + msg[0].flags = I3C_MSG_WRITE; + + msg[1].buf = (uint8_t *)buf; + msg[1].len = num_bytes; + msg[1].flags = I3C_MSG_WRITE | I3C_MSG_STOP; + + return i3c_transfer(target, msg, 2); +} + +/** + * @brief Read internal register of an I3C target device. + * + * This routine reads the value of an 8-bit internal register of an I3C target + * device synchronously. + * + * @param target I3C target device descriptor. + * @param reg_addr Address of the internal register being read. + * @param value Memory pool that stores the retrieved register value. + * + * @retval 0 If successful. + * @retval -EBUSY Bus is busy. + * @retval -EIO General input / output error. + */ +static inline int i3c_reg_read_byte(struct i3c_device_desc *target, + uint8_t reg_addr, uint8_t *value) +{ + return i3c_write_read(target, + ®_addr, sizeof(reg_addr), + value, sizeof(*value)); +} + +/** + * @brief Write internal register of an I3C target device. + * + * This routine writes a value to an 8-bit internal register of an I3C target + * device synchronously. + * + * @note This function internally combines the register and value into + * a single bus transaction. + * + * @param target I3C target device descriptor. + * @param reg_addr Address of the internal register being written. + * @param value Value to be written to internal register. + * + * @retval 0 If successful. + * @retval -EBUSY Bus is busy. + * @retval -EIO General input / output error. + */ +static inline int i3c_reg_write_byte(struct i3c_device_desc *target, + uint8_t reg_addr, uint8_t value) +{ + uint8_t tx_buf[2] = {reg_addr, value}; + + return i3c_write(target, tx_buf, 2); +} + +/** + * @brief Update internal register of an I3C target device. + * + * This routine updates the value of a set of bits from an 8-bit internal + * register of an I3C target device synchronously. + * + * @note If the calculated new register value matches the value that + * was read this function will not generate a write operation. + * + * @param target I3C target device descriptor. + * @param reg_addr Address of the internal register being updated. + * @param mask Bitmask for updating internal register. + * @param value Value for updating internal register. + * + * @retval 0 If successful. + * @retval -EBUSY Bus is busy. + * @retval -EIO General input / output error. + */ +static inline int i3c_reg_update_byte(struct i3c_device_desc *target, + uint8_t reg_addr, uint8_t mask, + uint8_t value) +{ + uint8_t old_value, new_value; + int rc; + + rc = i3c_reg_read_byte(target, reg_addr, &old_value); + if (rc != 0) { + return rc; + } + + new_value = (old_value & ~mask) | (value & mask); + if (new_value == old_value) { + return 0; + } + + return i3c_reg_write_byte(target, reg_addr, new_value); +} + +/** + * @brief Dump out an I3C message + * + * Dumps out a list of I3C messages. For any that are writes (W), the data is + * displayed in hex. + * + * It looks something like this (with name "testing"): + * + * D: I3C msg: testing, addr=56 + * D: W len=01: + * D: contents: + * D: 06 |. + * D: W len=0e: + * D: contents: + * D: 00 01 02 03 04 05 06 07 |........ + * D: 08 09 0a 0b 0c 0d |...... + * + * @param name Name of this dump, displayed at the top. + * @param msgs Array of messages to dump. + * @param num_msgs Number of messages to dump. + * @param target I3C target device descriptor. + */ +void i3c_dump_msgs(const char *name, const struct i3c_msg *msgs, + uint8_t num_msgs, struct i3c_device_desc *target); + +/** + * @brief Generic helper function to perform bus initialization. + * + * @param dev Pointer to controller device driver instance. + * @param i3c_dev_list Pointer to I3C device list. + * + * @retval 0 If successful. + * @retval -EBUSY Bus is busy. + * @retval -EIO General input / output error. + * @retval -ENODEV If a provisioned ID does not match to any target devices + * in the registered device list. + * @retval -ENOSPC No more free addresses can be assigned to target. + * @retval -ENOSYS Dynamic address assignment is not supported by + * the controller driver. + */ +int i3c_bus_init(const struct device *dev, + const struct i3c_dev_list *i3c_dev_list); + +/** + * @brief Get basic information from device and update device descriptor. + * + * This retrieves some basic information: + * * Bus Characteristics Register (GETBCR) + * * Device Characteristics Register (GETDCR) + * * Max Read Length (GETMRL) + * * Max Write Length (GETMWL) + * from the device and update the corresponding fields of the device + * descriptor. + * + * This only updates the field(s) in device descriptor + * only if CCC operations succeed. + * + * @param[in,out] target I3C target device descriptor. + * + * @retval 0 if successful. + * @retval -EIO General Input/Output error. + */ +int i3c_device_basic_info_get(struct i3c_device_desc *target); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#include + +#endif /* ZEPHYR_INCLUDE_DRIVERS_I3C_H_ */ diff --git a/include/zephyr/drivers/i3c/addresses.h b/include/zephyr/drivers/i3c/addresses.h new file mode 100644 index 00000000000..f259b735bba --- /dev/null +++ b/include/zephyr/drivers/i3c/addresses.h @@ -0,0 +1,167 @@ +/* + * Copyright 2022 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_I3C_ADDRESSES_H_ +#define ZEPHYR_INCLUDE_DRIVERS_I3C_ADDRESSES_H_ + +/** + * @brief I3C Address-related Helper Code + * @defgroup i3c_addresses I3C Address-related Helper Code + * @ingroup i3c_interface + * @{ + */ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define I3C_BROADCAST_ADDR 0x7E +#define I3C_MAX_ADDR 0x7F + +struct i3c_dev_list; + +enum i3c_addr_slot_status { + I3C_ADDR_SLOT_STATUS_FREE = 0U, + I3C_ADDR_SLOT_STATUS_RSVD, + I3C_ADDR_SLOT_STATUS_I3C_DEV, + I3C_ADDR_SLOT_STATUS_I2C_DEV, + I3C_ADDR_SLOT_STATUS_MASK = 0x03U, +}; + +/** + * @brief Structure to keep track of addresses on I3C bus. + */ +struct i3c_addr_slots { + /* 2 bits per slot */ + unsigned long slots[((I3C_MAX_ADDR + 1) * 2) / BITS_PER_LONG]; +}; + +/** + * @brief Initialize the I3C address slots struct. + * + * This clears out the assigned address bits, and set the reserved + * address bits according to the I3C specification. + * + * @param slots Pointer to address slots struct. + * @param dev_list Pointer to device list struct. + * + * @retval 0 if successful. + * @retval -EINVAL if duplicate addresses. + */ +int i3c_addr_slots_init(struct i3c_addr_slots *slots, + const struct i3c_dev_list *dev_list); + +/** + * @brief Set the address status of a device. + * + * @param slots Pointer to the address slots structure. + * @param dev_addr Device address. + * @param status New status for the address @p dev_addr. + */ +void i3c_addr_slots_set(struct i3c_addr_slots *slots, + uint8_t dev_addr, + enum i3c_addr_slot_status status); + +/** + * @brief Get the address status of a device. + * + * @param slots Pointer to the address slots structure. + * @param dev_addr Device address. + * + * @return Address status for the address @p dev_addr. + */ +enum i3c_addr_slot_status i3c_addr_slots_status(struct i3c_addr_slots *slots, + uint8_t dev_addr); + +/** + * @brief Check if the address is free. + * + * @param slots Pointer to the address slots structure. + * @param dev_addr Device address. + * + * @retval true if address is free. + * @retval false if address is not free. + */ +bool i3c_addr_slots_is_free(struct i3c_addr_slots *slots, + uint8_t dev_addr); + +/** + * @brief Find the next free address. + * + * This can be used to find the next free address that can be + * assigned to a new device. + * + * @param slots Pointer to the address slots structure. + * + * @return The next free address, or 0 if none found. + */ +uint8_t i3c_addr_slots_next_free_find(struct i3c_addr_slots *slots); + +/** + * @brief Mark the address as free (not used) in device list. + * + * @param addr_slots Pointer to the address slots struct. + * @param addr Device address. + */ +static inline void i3c_addr_slots_mark_free(struct i3c_addr_slots *addr_slots, + uint8_t addr) +{ + i3c_addr_slots_set(addr_slots, addr, + I3C_ADDR_SLOT_STATUS_FREE); +} + +/** + * @brief Mark the address as reserved in device list. + * + * @param addr_slots Pointer to the address slots struct. + * @param addr Device address. + */ +static inline void i3c_addr_slots_mark_rsvd(struct i3c_addr_slots *addr_slots, + uint8_t addr) +{ + i3c_addr_slots_set(addr_slots, addr, + I3C_ADDR_SLOT_STATUS_RSVD); +} + +/** + * @brief Mark the address as I3C device in device list. + * + * @param addr_slots Pointer to the address slots struct. + * @param addr Device address. + */ +static inline void i3c_addr_slots_mark_i3c(struct i3c_addr_slots *addr_slots, + uint8_t addr) +{ + i3c_addr_slots_set(addr_slots, addr, + I3C_ADDR_SLOT_STATUS_I3C_DEV); +} + +/** + * @brief Mark the address as I2C device in device list. + * + * @param addr_slots Pointer to the address slots struct. + * @param addr Device address. + */ +static inline void i3c_addr_slots_mark_i2c(struct i3c_addr_slots *addr_slots, + uint8_t addr) +{ + i3c_addr_slots_set(addr_slots, addr, + I3C_ADDR_SLOT_STATUS_I2C_DEV); +} + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_DRIVERS_I3C_ADDRESSES_H_ */ diff --git a/include/zephyr/drivers/i3c/ccc.h b/include/zephyr/drivers/i3c/ccc.h new file mode 100644 index 00000000000..033e2038ad6 --- /dev/null +++ b/include/zephyr/drivers/i3c/ccc.h @@ -0,0 +1,1064 @@ +/* + * Copyright 2022 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_I3C_CCC_H_ +#define ZEPHYR_INCLUDE_DRIVERS_I3C_CCC_H_ + +/** + * @brief I3C Common Command Codes + * @defgroup i3c_ccc I3C Common Command Codes + * @ingroup i3c_interface + * @{ + */ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Maximum CCC ID for broadcast */ +#define I3C_CCC_BROADCAST_MAX_ID 0x7FU + +/** + * Enable Events Command + * + * @param broadcast True if broadcast, false if direct. + */ +#define I3C_CCC_ENEC(broadcast) ((broadcast) ? 0x00U : 0x80U) + +/** + * Disable Events Command + * + * @param broadcast True if broadcast, false if direct. + */ +#define I3C_CCC_DISEC(broadcast) ((broadcast) ? 0x01U : 0x81U) + +/** + * Enter Activity State + * + * @param as Desired activity state + * @param broadcast True if broadcast, false if direct. + */ +#define I3C_CCC_ENTAS(as, broadcast) (((broadcast) ? 0x02U : 0x82U) + (as)) + +/** + * Enter Activity State 0 + * + * @param broadcast True if broadcast, false if direct. + */ +#define I3C_CCC_ENTAS0(broadcast) I3C_CCC_ENTAS(0, broadcast) + +/** + * Enter Activity State 1 + * + * @param broadcast True if broadcast, false if direct. + */ +#define I3C_CCC_ENTAS1(broadcast) I3C_CCC_ENTAS(1, broadcast) + +/** + * Enter Activity State 2 + * + * @param broadcast True if broadcast, false if direct. + */ +#define I3C_CCC_ENTAS2(broadcast) I3C_CCC_ENTAS(2, broadcast) + +/** + * Enter Activity State 3 + * + * @param broadcast True if broadcast, false if direct. + */ +#define I3C_CCC_ENTAS3(broadcast) I3C_CCC_ENTAS(3, broadcast) + +/** Reset Dynamic Address Assignment (Broadcast) */ +#define I3C_CCC_RSTDAA 0x06U + +/** Enter Dynamic Address Assignment (Broadcast) */ +#define I3C_CCC_ENTDAA 0x07U + +/** Define List of Targets (Broadcast) */ +#define I3C_CCC_DEFTGTS 0x08U + +/** + * Set Max Write Length (Broadcast or Direct) + * + * @param broadcast True if broadcast, false if direct. + */ +#define I3C_CCC_SETMWL(broadcast) ((broadcast) ? 0x09U : 0x89U) + +/** + * Set Max Read Length (Broadcast or Direct) + * + * @param broadcast True if broadcast, false if direct. + */ +#define I3C_CCC_SETMRL(broadcast) ((broadcast) ? 0x0AU : 0x8AU) + +/** Enter Test Mode (Broadcast) */ +#define I3C_CCC_ENTTM 0x0BU + +/** Set Bus Context (Broadcast) */ +#define I3C_CCC_SETBUSCON 0x0CU + +/** + * Data Transfer Ending Procedure Control + * + * @param broadcast True if broadcast, false if direct. + */ +#define I3C_CCC_ENDXFER(broadcast) ((broadcast) ? 0x12U : 0x92U) + +/** Enter HDR Mode (HDR-DDR) (Broadcast) */ +#define I3C_CCC_ENTHDR(x) (0x20U + (x)) + +/** Enter HDR Mode 0 (HDR-DDR) (Broadcast) */ +#define I3C_CCC_ENTHDR0 0x20U + +/** Enter HDR Mode 1 (HDR-TSP) (Broadcast) */ +#define I3C_CCC_ENTHDR1 0x21U + +/** Enter HDR Mode 2 (HDR-TSL) (Broadcast) */ +#define I3C_CCC_ENTHDR2 0x22U + +/** Enter HDR Mode 3 (HDR-BT) (Broadcast) */ +#define I3C_CCC_ENTHDR3 0x23U + +/** Enter HDR Mode 4 (Broadcast) */ +#define I3C_CCC_ENTHDR4 0x24U + +/** Enter HDR Mode 5 (Broadcast) */ +#define I3C_CCC_ENTHDR5 0x25U + +/** Enter HDR Mode 6 (Broadcast) */ +#define I3C_CCC_ENTHDR6 0x26U + +/** Enter HDR Mode 7 (Broadcast) */ +#define I3C_CCC_ENTHDR7 0x27U + +/** + * Exchange Timing Information (Broadcast or Direct) + * + * @param broadcast True if broadcast, false if direct. + */ +#define I3C_CCC_SETXTIME(broadcast) ((broadcast) ? 0x28U : 0x98U) + +/** Set All Addresses to Static Addresses (Broadcast) */ +#define I3C_CCC_SETAASA 0x29U + +/** + * Target Reset Action + * + * @param broadcast True if broadcast, false if direct. + */ +#define I3C_CCC_RSTACT(broadcast) ((broadcast) ? 0x2AU : 0x9AU) + +/** Define List of Group Address (Broadcast) */ +#define I3C_CCC_DEFGRPA 0x2BU + +/** + * Reset Group Address + * + * @param broadcast True if broadcast, false if direct. + */ +#define I3C_CCC_RSTGRPA(broadcast) ((broadcast) ? 0x2CU : 0x9CU) + +/** Multi-Lane Data Transfer Control (Broadcast) */ +#define I3C_CCC_MLANE(broadcast) ((broadcast) ? 0x2DU : 0x9DU) + +/** + * Vendor/Standard Extension + * + * @param broadcast True if broadcast, false if direct. + * @param id Extension ID. + */ +#define I3C_CCC_VENDOR(broadcast, id) ((id) + ((broadcast) ? 0x61U : 0xE0U)) + +/** Set Dynamic Address from Static Address (Direct) */ +#define I3C_CCC_SETDASA 0x87U + +/** Set New Dynamic Address (Direct) */ +#define I3C_CCC_SETNEWDA 0x88U + +/** Get Max Write Length (Direct) */ +#define I3C_CCC_GETMWL 0x8BU + +/** Get Max Read Length (Direct) */ +#define I3C_CCC_GETMRL 0x8CU + +/** Get Provisioned ID (Direct) */ +#define I3C_CCC_GETPID 0x8DU + +/** Get Bus Characteristics Register (Direct) */ +#define I3C_CCC_GETBCR 0x8EU + +/** Get Device Characteristics Register (Direct) */ +#define I3C_CCC_GETDCR 0x8FU + +/** Get Device Status (Direct) */ +#define I3C_CCC_GETSTATUS 0x90U + +/** Get Accept Controller Role (Direct) */ +#define I3C_CCC_GETACCCR 0x91U + +/** Set Bridge Targets (Direct) */ +#define I3C_CCC_SETBRGTGT 0x93U + +/** Get Max Data Speed (Direct) */ +#define I3C_CCC_GETMXDS 0x94U + +/** Get Optional Feature Capabilities (Direct) */ +#define I3C_CCC_GETCAPS 0x95U + +/** Set Route (Direct) */ +#define I3C_CCC_SETROUTE 0x96U + +/** Device to Device(s) Tunneling Control (Direct) */ +#define I3C_CCC_D2DXFER 0x97U + +/** Get Exchange Timing Information (Direct) */ +#define I3C_CCC_GETXTIME 0x99U + +/** Set Group Address (Direct) */ +#define I3C_CCC_SETGRPA 0x9BU + +struct i3c_device_desc; + +/** + * @brief Payload structure for Direct CCC to one target. + */ +struct i3c_ccc_target_payload { + /** Target address */ + uint8_t addr; + + /** @c 0 for Write, @c 1 for Read */ + uint8_t rnw:1; + + /** + * - For Write CCC, pointer to the byte array of data + * to be sent, which may contain the Sub-Command Byte + * and additional data. + * - For Read CCC, pointer to the byte buffer for data + * to be read into. + */ + uint8_t *data; + + /** Length in bytes for @p data. */ + size_t data_len; +}; + +/** + * @brief Payload structure for one CCC transaction. + */ +struct i3c_ccc_payload { + struct { + /** + * The CCC ID (@c I3C_CCC_*). + */ + uint8_t id; + + /** + * Pointer to byte array of data for this CCC. + * + * This is the bytes following the CCC command in CCC frame. + * Set to @c NULL if no associated data. + */ + uint8_t *data; + + /** Length in bytes for optional data array. */ + size_t data_len; + } ccc; + + struct { + /** + * Array of struct i3c_ccc_target_payload. + * + * Each element describes the target and associated + * payloads for this CCC. + * + * Use with Direct CCC. + */ + struct i3c_ccc_target_payload *payloads; + + /** Number of targets */ + size_t num_targets; + } targets; +}; + +/** + * @brief Payload for ENEC/DISEC CCC (Target Events Command). + */ +struct i3c_ccc_events { + /** + * Event byte: + * - Bit[0]: ENINT/DISINT: + * - Target Interrupt Requests + * - Bit[1]: ENCR/DISCR: + * - Controller Role Requests + * - Bit[3]: ENHJ/DISHJ: + * - Hot-Join Event + */ + uint8_t events; +} __packed; + +/* For Enable Events */ +#define I3C_CCC_ENEC_EVT_ENINTR BIT(0) +#define I3C_CCC_ENEC_EVT_ENCR BIT(1) +#define I3C_CCC_ENEC_EVT_ENHJ BIT(3) + +#define I3C_CCC_ENEC_EVT_ALL \ + (I3C_CCC_ENEC_EVT_ENINTR | I3C_CCC_ENEC_EVT_ENCR | I3C_CCC_ENEC_EVT_ENHJ) + +/* For Disable Events */ +#define I3C_CCC_DISEC_EVT_DISINTR BIT(0) +#define I3C_CCC_DISEC_EVT_DISCR BIT(1) +#define I3C_CCC_DISEC_EVT_DISHJ BIT(3) + +#define I3C_CCC_DISEC_EVT_ALL \ + (I3C_CCC_DISEC_EVT_DISINTR | I3C_CCC_DISEC_EVT_DISCR | I3C_CCC_DISEC_EVT_DISHJ) + +/* + * Events for both enabling and disabling since + * they have the same bits. + */ +#define I3C_CCC_EVT_INTR BIT(0) +#define I3C_CCC_EVT_CR BIT(1) +#define I3C_CCC_EVT_HJ BIT(3) + +#define I3C_CCC_EVT_ALL \ + (I3C_CCC_EVT_INTR | I3C_CCC_EVT_CR | I3C_CCC_EVT_HJ) + +/** + * @brief Payload for SETMWL/GETMWL CCC (Set/Get Maximum Write Length). + * + * @note For drivers and help functions, the raw data coming + * back from target device is in big endian. This needs to be + * translated back to CPU endianness before passing back to + * function caller. + */ +struct i3c_ccc_mwl { + /** Maximum Write Length */ + uint16_t len; +} __packed; + +/** + * @brief Payload for SETMRL/GETMRL CCC (Set/Get Maximum Read Length). + * + * @note For drivers and help functions, the raw data coming + * back from target device is in big endian. This needs to be + * translated back to CPU endianness before passing back to + * function caller. + */ +struct i3c_ccc_mrl { + /** Maximum Read Length */ + uint16_t len; + + /** Optional IBI Payload Size */ + uint8_t ibi_len; +} __packed; + +/** + * @brief The active controller part of payload for DEFTGTS CCC. + * + * This is used by DEFTGTS (Define List of Targets) CCC to describe + * the active controller on the I3C bus. + */ +struct i3c_ccc_deftgts_active_controller { + /** Dynamic Address of Active Controller */ + uint8_t addr; + + /** Device Characteristic Register of Active Controller */ + uint8_t dcr; + + /** Bus Characteristic Register of Active Controller */ + uint8_t bcr; + + /** Static Address of Active Controller */ + uint8_t static_addr; +}; + +/** + * @brief The target device part of payload for DEFTGTS CCC. + * + * This is used by DEFTGTS (Define List of Targets) CCC to describe + * the existing target devices on the I3C bus. + */ +struct i3c_ccc_deftgts_target { + /** Dynamic Address of a target device, or a group address */ + uint8_t addr; + + union { + /** + * Device Characteristic Register of a I3C target device + * or a group. + */ + uint8_t dcr; + + /** Legacy Virtual Register for legacy I2C device. */ + uint8_t lvr; + }; + + /** Bus Characteristic Register of a target device or a group */ + uint8_t bcr; + + /** Static Address of a target device or a group */ + uint8_t static_addr; +}; + +/** + * @brief Payload for DEFTGTS CCC (Define List of Targets). + * + * @note @p i3c_ccc_deftgts_target is an array of targets, where + * the number of elements is dependent on the number of I3C targets + * on the bus. Please have enough space for both read and write of + * this CCC. + */ +struct i3c_ccc_deftgts { + /** Data describing the active controller */ + struct i3c_ccc_deftgts_active_controller active_controller; + + /** Data describing the target(s) on the bus */ + struct i3c_ccc_deftgts_target targets[]; +} __packed; + +/** + * @brief Payload for a single device address. + * + * This is used for: + * - SETDASA (Set Dynamic Address from Static Address) + * - SETNEWDA (Set New Dynamic Address) + * - SETGRPA (Set Group Address) + * - GETACCCR (Get Accept Controller Role) + * + * Note that the target address is encoded within + * struct i3c_ccc_target_payload instead of being encoded in + * this payload. + */ +struct i3c_ccc_address { + /** + * - For SETDASA, Static Address to be assigned as + * Dynamic Address. + * - For SETNEWDA, new Dynamic Address to be assigned. + * - For SETGRPA, new Group Address to be set. + * - For GETACCCR, the correct address of Secondary + * Controller. + * + * @note For SETDATA, SETNEWDA and SETGRAP, + * the address is left-shift by 1, and bit[0] is always 0. + * + * @note Fpr SET GETACCCR, the address is left-shift by 1, + * and bit[0] is the calculated odd parity bit. + */ + uint8_t addr; +} __packed; + +/** + * @brief Payload for GETPID CCC (Get Provisioned ID). + */ +struct i3c_ccc_getpid { + /** + * 48-bit Provisioned ID. + * + * @note Data is big-endian where first byte is MSB. + */ + uint8_t pid[6]; +} __packed; + +/** + * @brief Payload for GETBCR CCC (Get Bus Characteristics Register). + */ +struct i3c_ccc_getbcr { + /** Bus Characteristics Register */ + uint8_t bcr; +} __packed; + +/** + * @brief Payload for GETDCR CCC (Get Device Characteristics Register). + */ +struct i3c_ccc_getdcr { + /** Device Characteristics Register */ + uint8_t dcr; +} __packed; + + +/** + * @brief Indicate which format of GETSTATUS to use. + */ +enum i3c_ccc_getstatus_fmt { + GETSTATUS_FORMAT_1, + GETSTATUS_FORMAT_2, +}; + +enum i3c_ccc_getstatus_defbyte { + GETSTATUS_FORMAT_2_TGTSTAT = 0x00U, + GETSTATUS_FORMAT_2_PRECR = 0x91U, + + GETSTATUS_FORMAT_2_INVALID = 0x100U +}; + +/** + * @brief Payload for GETSTATUS CCC (Get Device Status). + */ +union i3c_ccc_getstatus { + struct { + /** + * Device Status + * - Bit[15:8]: Reserved. + * - Bit[7:6]: Activity Mode. + * - Bit[5]: Protocol Error. + * - Bit[4]: Reserved. + * - Bit[3:0]: Number of Pending Interrupts. + * + * @note For drivers and help functions, the raw data coming + * back from target device is in big endian. This needs to be + * translated back to CPU endianness before passing back to + * function caller. + */ + uint16_t status; + } fmt1; + + union { + /** + * Defining Byte 0x00: TGTSTAT + * + * @see i3c_ccc_getstatus::fmt1::status + */ + uint16_t tgtstat; + + /** + * Defining Byte 0x91: PRECR + * - Bit[15:8]: Vendor Reserved + * - Bit[7:2]: Reserved + * - Bit[1]: Handoff Delay NACK + * - Bit[0]: Deep Sleep Detected + * + * @note For drivers and help functions, the raw data coming + * back from target device is in big endian. This needs to be + * translated back to CPU endianness before passing back to + * function caller. + */ + uint16_t precr; + + uint16_t raw_u16; + } fmt2; +} __packed; + +#define I3C_CCC_GETSTATUS_PROTOCOL_ERR BIT(5) + +#define I3C_CCC_GETSTATUS_ACTIVITY_MODE_SHIFT 6 + +#define I3C_CCC_GETSTATUS_ACTIVITY_MODE_MASK \ + (0x03U << I3C_CCC_GETSTATUS_ACTIVITY_MODE_SHIFT) + +#define I3C_CCC_GETSTATUS_ACTIVITY_MODE(status) \ + (((status) & I3C_CCC_GETSTATUS_ACTIVITY_MODE_MASK) \ + >> I3C_CCC_GETSTATUS_ACTIVITY_MODE_SHIFT) + +#define I3C_CCC_GETSTATUS_NUM_INT_SHIFT 0 + +#define I3C_CCC_GETSTATUS_NUM_INT_MASK \ + (0x0FU << I3C_CCC_GETSTATUS_NUM_INT_SHIFT) + +#define I3C_CCC_GETSTATUS_NUM_INT(status) \ + (((status) & I3C_CCC_GETSTATUS_NUM_INT_MASK) \ + >> I3C_CCC_GETSTATUS_NUM_INT_SHIFT) + +#define I3C_CCC_GETSTATUS_PRECR_DEEP_SLEEP_DETECTED BIT(0) + +#define I3C_CCC_GETSTATUS_PRECR_HANDOFF_DELAY_NACK BIT(1) + +/** + * @brief One Bridged Target for SETBRGTGT payload. + */ +struct i3c_ccc_setbrgtgt_tgt { + /** + * Dynamic address of the bridged target. + * + * @note The address is left-shift by 1, and bit[0] + * is always 0. + */ + uint8_t addr; + + /** + * 16-bit ID for the bridged target. + * + * @note For drivers and help functions, the raw data coming + * back from target device is in big endian. This needs to be + * translated back to CPU endianness before passing back to + * function caller. + */ + uint16_t id; +} __packed; + +/** + * @brief Payload for SETBRGTGT CCC (Set Bridge Targets). + * + * Note that the bridge target address is encoded within + * struct i3c_ccc_target_payload instead of being encoded in + * this payload. + */ +struct i3c_ccc_setbrgtgt { + /** Number of bridged targets */ + uint8_t count; + + /** Array of bridged targets */ + struct i3c_ccc_setbrgtgt_tgt targets[]; +} __packed; + +/** + * @brief Payload for GETMXDS CCC (Get Max Data Speed). + * + * @note This is only for GETMXDS Format 1 and Format 2. + */ +union i3c_ccc_getmxds { + struct { + /** maxWr */ + uint8_t maxwr; + + /** maxRd */ + uint8_t maxrd; + } fmt1; + + struct { + /** maxWr */ + uint8_t maxwr; + + /** maxRd */ + uint8_t maxrd; + + /** + * Maximum Read Turnaround Time in microsecond. + * + * This is in little-endian where first byte is LSB. + */ + uint8_t maxrdturn[3]; + } fmt2; + + struct { + /** + * Defining Byte 0x00: WRRDTURN + * + * @see i3c_ccc_getmxds::fmt2 + */ + uint8_t wrrdturn; + + /** + * Defining Byte 0x91: CRHDLY + * - Bit[2]: Set Bus Actibity State + * - Bit[1:0]: Controller Handoff Activity State + */ + uint8_t crhdly1; + } fmt3; +} __packed; + +#define I3C_CCC_GETMXDS_MAX_SDR_FSCL_MAX 0 +#define I3C_CCC_GETMXDS_MAX_SDR_FSCL_8MHZ 1 +#define I3C_CCC_GETMXDS_MAX_SDR_FSCL_6MHZ 2 +#define I3C_CCC_GETMXDS_MAX_SDR_FSCL_4MHZ 3 +#define I3C_CCC_GETMXDS_MAX_SDR_FSCL_2MHZ 4 + +#define I3C_CCC_GETMXDS_TSCO_8NS 0 +#define I3C_CCC_GETMXDS_TSCO_9NS 1 +#define I3C_CCC_GETMXDS_TSCO_10NS 2 +#define I3C_CCC_GETMXDS_TSCO_11NS 3 +#define I3C_CCC_GETMXDS_TSCO_12NS 4 +#define I3C_CCC_GETMXDS_TSCO_GT_12NS 7 + +#define I3C_CCC_GETMXDS_MAXWR_DEFINING_BYTE_SUPPORT BIT(3) + +#define I3C_CCC_GETMXDS_MAXWR_MAX_SDR_FSCL_SHIFT 0 + +#define I3C_CCC_GETMXDS_MAXWR_MAX_SDR_FSCL_MASK \ + (0x07U << I3C_CCC_GET_MXDS_MAXWR_MAX_SDR_FSCL_SHIFT) + +#define I3C_CCC_GETMXDS_MAXWR_MAX_SDR_FSCL(maxwr) \ + (((maxwr) & \ + I3C_CCC_GETMXDS_MAXWR_MAX_SDR_FSCL_MASK) \ + >> I3C_CCC_GETMXDS_MAXWR_MAX_SDR_FSCL_SHIFT) + +#define I3C_CCC_GETMXDS_MAXRD_W2R_PERMITS_STOP_BETWEEN BIT(6) + +#define I3C_CCC_GETMXDS_MAXRD_TSCO_SHIFT 3 + +#define I3C_CCC_GETMXDS_MAXRD_TSCO_MASK \ + (0x07U << I3C_CCC_GETMXDS_MAXRD_TSCO_SHIFT) + +#define I3C_CCC_GETMXDS_MAXRD_TSCO(maxrd) \ + (((maxrd) & I3C_CCC_GETMXDS_MAXRD_TSCO_MASK) \ + >> I3C_CCC_GETMXDS_MAXRD_TSCO_SHIFT) + +#define I3C_CCC_GETMXDS_MAXRD_MAX_SDR_FSCL_SHIFT 0 + +#define I3C_CCC_GETMXDS_MAXRD_MAX_SDR_FSCL_MASK \ + (0x07U << I3C_CCC_GET_MXDS_MAXRD_MAX_SDR_FSCL_SHIFT) + +#define I3C_CCC_GETMXDS_MAXRD_MAX_SDR_FSCL(maxrd) \ + (((maxrd) & \ + I3C_CCC_GETMXDS_MAXRD_MAX_SDR_FSCL_MASK) \ + >> I3C_CCC_GETMXDS_MAXRD_MAX_SDR_FSCL_SHIFT) + +#define I3C_CCC_GETMXDS_CRDHLY1_SET_BUS_ACT_STATE BIT(2) + +#define I3C_CCC_GETMXDS_CRDHLY1_CTRL_HANDOFF_ACT_STATE_SHIFT 0 + +#define I3C_CCC_GETMXDS_CRDHLY1_CTRL_HANDOFF_ACT_STATE_MASK \ + (0x03U << I3C_CCC_GETMXDS_CRDHLY1_SET_BUS_ACT_STATE_SHIFT) + +#define I3C_CCC_GETMXDS_CRDHLY1_CTRL_HANDOFF_ACT_STATE(crhdly1) \ + (((crhdly1) & \ + I3C_CCC_GETMXDS_CRDHLY1_SET_BUS_ACT_STATE_MASK) \ + >> I3C_CCC_GETMXDS_CRDHLY1_SET_BUS_ACT_STATE_SHIFT) + +/** + * @brief Payload for GETCAPS CCC (Get Optional Feature Capabilities). + * + * @note Only support GETCAPS Format 1. + */ +struct i3c_ccc_getcaps { + /** + * GETCAP[1-4] bytes. + */ + uint8_t getcaps[4]; +} __packed; + +#define I3C_CCC_GETCAPS1_HDR_DDR BIT(0) +#define I3C_CCC_GETCAPS1_HDR_BT BIT(3) + +#define I3C_CCC_GETCAPS1_HDR_MODE(x) BIT(x) +#define I3C_CCC_GETCAPS1_HDR_MODE0 BIT(0) +#define I3C_CCC_GETCAPS1_HDR_MODE1 BIT(1) +#define I3C_CCC_GETCAPS1_HDR_MODE2 BIT(2) +#define I3C_CCC_GETCAPS1_HDR_MODE3 BIT(3) +#define I3C_CCC_GETCAPS1_HDR_MODE4 BIT(4) +#define I3C_CCC_GETCAPS1_HDR_MODE5 BIT(5) +#define I3C_CCC_GETCAPS1_HDR_MODE6 BIT(6) +#define I3C_CCC_GETCAPS1_HDR_MODE7 BIT(7) + +#define I3C_CCC_GETCAPS2_HDRDDR_WRITE_ABORT BIT(6) +#define I3C_CCC_GETCAPS2_HDRDDR_ABORT_CRC BIT(7) + +#define I3C_CCC_GETCAPS2_GRPADDR_CAP_SHIFT 4 +#define I3C_CCC_GETCAPS2_GRPADDR_CAP_MASK \ + (0x03U << I3C_CCC_GETCAPS2_GRPADDR_CAP_SHIFT) +#define I3C_CCC_GETCAPS2_GRPADDR_CAP(getcaps2) \ + (((getcaps2) & \ + I3C_CCC_GETCAPS2_GRPADDR_CAP_MASK) \ + >> I3C_CCC_GETCAPS_GRPADDR_CAP_SHIFT) + +#define I3C_CCC_GETCAPS2_SPEC_VER_SHIFT 0 +#define I3C_CCC_GETCAPS2_SPEC_VER_MASK \ + (0x0FU << I3C_CCC_GETCAPS2_SPEC_VER_SHIFT) +#define I3C_CCC_GETCAPS2_SPEC_VER(getcaps2) \ + (((getcaps2) & \ + I3C_CCC_GETCAPS2_SPEC_VER_MASK) \ + >> I3C_CCC_GETCAPS_SPEC_VER_SHIFT) + +#define I3C_CCC_GETCAPS3_MLAME_SUPPORT BIT(0) +#define I3C_CCC_GETCAPS3_D2DXFER_SUPPORT BIT(1) +#define I3C_CCC_GETCAPS3_D3DXFER_IBI_CAPABLE BIT(2) +#define I3C_CCC_GETCAPS3_GETCAPS_DEFINING_BYTE_SUPPORT BIT(3) +#define I3C_CCC_GETCAPS3_GETSTATUS_DEFINING_BYTE_SUPPORT BIT(4) +#define I3C_CCC_GETCAPS3_HDRBT_CRC32_SUPPORT BIT(5) +#define I3C_CCC_GETCAPS3_IBI_MDR_PENDING_READ_NOTIFICATION BIT(6) + +enum i3c_ccc_rstact_defining_byte { + I3C_CCC_RSTACT_NO_RESET = 0x00U, + I3C_CCC_RSTACT_PERIPHERAL_ONLY = 0x01U, + I3C_CCC_RSTACT_RESET_WHOLE_TARGET = 0x02U, + I3C_CCC_RSTACT_DEBUG_NETWORK_ADAPTER = 0x03U, + I3C_CCC_RSTACT_VIRTUAL_TARGET_DETECT = 0x04U, +}; + +/** + * @brief Test if I3C CCC payload is for broadcast. + * + * This tests if the CCC payload is for broadcast. + * + * @param[in] payload Pointer to the CCC payload. + * + * @retval true if payload target is broadcast + * @retval false if payload target is direct + */ +static inline bool i3c_ccc_is_payload_broadcast(const struct i3c_ccc_payload *payload) +{ + return (payload->ccc.id <= I3C_CCC_BROADCAST_MAX_ID); +} + +/** + * @brief Get BCR from a target + * + * Helper function to get BCR (Bus Characteristic Register) from + * target device. + * + * @param[in] target Pointer to the target device descriptor. + * @param[out] bcr Pointer to the BCR payload structure. + * + * @return @see i3c_do_ccc + */ +int i3c_ccc_do_getbcr(struct i3c_device_desc *target, + struct i3c_ccc_getbcr *bcr); + +/** + * @brief Get DCR from a target + * + * Helper function to get DCR (Device Characteristic Register) from + * target device. + * + * @param[in] target Pointer to the target device descriptor. + * @param[out] dcr Pointer to the DCR payload structure. + * + * @return @see i3c_do_ccc + */ +int i3c_ccc_do_getdcr(struct i3c_device_desc *target, + struct i3c_ccc_getdcr *dcr); + +/** + * @brief Get PID from a target + * + * Helper function to get PID (Provisioned ID) from + * target device. + * + * @param[in] target Pointer to the target device descriptor. + * @param[out] pid Pointer to the PID payload structure. + * + * @return @see i3c_do_ccc + */ +int i3c_ccc_do_getpid(struct i3c_device_desc *target, + struct i3c_ccc_getpid *pid); + +/** + * @brief Broadcast RSTACT to reset I3C Peripheral. + * + * Helper function to broadcast Target Reset Action (RSTACT) to + * all connected targets to Reset the I3C Peripheral Only (0x01). + * + * @param[in] controller Pointer to the controller device driver instance. + * @param[in] action What reset action to perform. + * + * @return @see i3c_do_ccc + */ +int i3c_ccc_do_rstact_all(const struct device *controller, + enum i3c_ccc_rstact_defining_byte action); + +/** + * @brief Broadcast RSTDAA to reset dynamic addresses for all targets. + * + * Helper function to reset dynamic addresses of all connected targets. + * + * @param[in] controller Pointer to the controller device driver instance. + * + * @return @see i3c_do_ccc + */ +int i3c_ccc_do_rstdaa_all(const struct device *controller); + +/** + * @brief Set Dynamic Address from Static Address for a target + * + * Helper function to do SETDASA (Set Dynamic Address from Static Address) + * for a particular target. + * + * Note this does not update @p target with the new dynamic address. + * + * @param[in] target Pointer to the target device descriptor where + * the device is configured with a static address. + * + * @return @see i3c_do_ccc + */ +int i3c_ccc_do_setdasa(const struct i3c_device_desc *target); + +/** + * @brief Broadcast ENEC/DISEC to enable/disable target events. + * + * Helper function to broadcast Target Events Command to enable or + * disable target events (ENEC/DISEC). + * + * @param[in] controller Pointer to the controller device driver instance. + * @param[in] enable ENEC if true, DISEC if false. + * @param[in] events Pointer to the event struct. + * + * @return @see i3c_do_ccc + */ +int i3c_ccc_do_events_all_set(const struct device *controller, + bool enable, struct i3c_ccc_events *events); + +/** + * @brief Direct CCC ENEC/DISEC to enable/disable target events. + * + * Helper function to send Target Events Command to enable or + * disable target events (ENEC/DISEC) on a single target. + * + * @param[in] target Pointer to the target device descriptor. + * @param[in] enable ENEC if true, DISEC if false. + * @param[in] events Pointer to the event struct. + * + * @return @see i3c_do_ccc + */ +int i3c_ccc_do_events_set(struct i3c_device_desc *target, + bool enable, struct i3c_ccc_events *events); + +/** + * @brief Broadcast SETMWL to Set Maximum Write Length. + * + * Helper function to do SETMWL (Set Maximum Write Length) to + * all connected targets. + * + * @param[in] controller Pointer to the controller device driver instance. + * @param[in] mwl Pointer to SETMWL payload. + * + * @return @see i3c_do_ccc + */ +int i3c_ccc_do_setmwl_all(const struct device *controller, + const struct i3c_ccc_mwl *mwl); + +/** + * @brief Single target SETMWL to Set Maximum Write Length. + * + * Helper function to do SETMWL (Set Maximum Write Length) to + * one target. + * + * @param[in] target Pointer to the target device descriptor. + * @param[in] mwl Pointer to SETMWL payload. + * + * @return @see i3c_do_ccc + */ +int i3c_ccc_do_setmwl(const struct i3c_device_desc *target, + const struct i3c_ccc_mwl *mwl); + +/** + * @brief Single target GETMWL to Get Maximum Write Length. + * + * Helper function to do GETMWL (Get Maximum Write Length) of + * one target. + * + * @param[in] target Pointer to the target device descriptor. + * @param[out] mwl Pointer to GETMWL payload. + * + * @return @see i3c_do_ccc + */ +int i3c_ccc_do_getmwl(const struct i3c_device_desc *target, + struct i3c_ccc_mwl *mwl); + +/** + * @brief Broadcast SETMRL to Set Maximum Read Length. + * + * Helper function to do SETMRL (Set Maximum Read Length) to + * all connected targets. + * + * @param[in] controller Pointer to the controller device driver instance. + * @param[in] mrl Pointer to SETMRL payload. + * @param[in] has_ibi_size True if also sending the optional IBI payload + * size. False if not sending. + * + * @return @see i3c_do_ccc + */ +int i3c_ccc_do_setmrl_all(const struct device *controller, + const struct i3c_ccc_mrl *mrl, + bool has_ibi_size); + +/** + * @brief Single target SETMRL to Set Maximum Read Length. + * + * Helper function to do SETMRL (Set Maximum Read Length) to + * one target. + * + * Note this uses the BCR of the target to determine whether + * to send the optional IBI payload size. + * + * @param[in] target Pointer to the target device descriptor. + * @param[in] mrl Pointer to SETMRL payload. + * + * @return @see i3c_do_ccc + */ +int i3c_ccc_do_setmrl(const struct i3c_device_desc *target, + const struct i3c_ccc_mrl *mrl); + +/** + * @brief Single target GETMRL to Get Maximum Read Length. + * + * Helper function to do GETMRL (Get Maximum Read Length) of + * one target. + * + * Note this uses the BCR of the target to determine whether + * to send the optional IBI payload size. + * + * @param[in] target Pointer to the target device descriptor. + * @param[out] mrl Pointer to GETMRL payload. + * + * @return @see i3c_do_ccc + */ +int i3c_ccc_do_getmrl(const struct i3c_device_desc *target, + struct i3c_ccc_mrl *mrl); + +/** + * @brief Single target GETSTATUS to Get Target Status. + * + * Helper function to do GETSTATUS (Get Target Status) of + * one target. + * + * Note this uses the BCR of the target to determine whether + * to send the optional IBI payload size. + * + * @param[in] target Pointer to the target device descriptor. + * @param[out] status Pointer to GETSTATUS payload. + * @param[in] fmt Which GETSTATUS to use. + * @param[in] defbyte Defining Byte if using format 2. + * + * @return @see i3c_do_ccc + */ +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); + +/** + * @brief Single target GETSTATUS to Get Target Status (Format 1). + * + * Helper function to do GETSTATUS (Get Target Status, format 1) of + * one target. + * + * @param[in] target Pointer to the target device descriptor. + * @param[out] status Pointer to GETSTATUS payload. + * + * @return @see i3c_do_ccc + */ +static inline int i3c_ccc_do_getstatus_fmt1(const struct i3c_device_desc *target, + union i3c_ccc_getstatus *status) +{ + return i3c_ccc_do_getstatus(target, status, + GETSTATUS_FORMAT_1, + GETSTATUS_FORMAT_2_INVALID); +} + +/** + * @brief Single target GETSTATUS to Get Target Status (Format 2). + * + * Helper function to do GETSTATUS (Get Target Status, format 2) of + * one target. + * + * @param[in] target Pointer to the target device descriptor. + * @param[out] status Pointer to GETSTATUS payload. + * @param[in] defbyte Defining Byte for GETSTATUS format 2. + * + * @return @see i3c_do_ccc + */ +static inline int i3c_ccc_do_getstatus_fmt2(const struct i3c_device_desc *target, + union i3c_ccc_getstatus *status, + enum i3c_ccc_getstatus_defbyte defbyte) +{ + return i3c_ccc_do_getstatus(target, status, + GETSTATUS_FORMAT_2, defbyte); +} + + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_DRIVERS_I3C_CCC_H_ */ diff --git a/include/zephyr/drivers/i3c/devicetree.h b/include/zephyr/drivers/i3c/devicetree.h new file mode 100644 index 00000000000..fc2829a6c1e --- /dev/null +++ b/include/zephyr/drivers/i3c/devicetree.h @@ -0,0 +1,236 @@ +/* + * Copyright 2022 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_I3C_DEVICETREE_H_ +#define ZEPHYR_INCLUDE_DRIVERS_I3C_DEVICETREE_H_ + +/** + * @brief I3C Devicetree related bits + * @defgroup i3c_devicetree I3C Devicetree related bits + * @ingroup i3c_interface + * @{ + */ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Structure initializer for i3c_device_id from devicetree + * + * This helper macro expands to a static initializer for a struct + * i3c_device_id by reading the relevant device data from devicetree. + * + * @param node_id Devicetree node identifier for the I3C device whose + * struct i3c_device_id to create an initializer for + */ +#define I3C_DEVICE_ID_DT(node_id) \ + { \ + .pid = ((uint64_t)DT_PROP_BY_IDX(node_id, reg, 1) << 32)\ + | DT_PROP_BY_IDX(node_id, reg, 2), \ + } + +/** + * @brief Structure initializer for i3c_device_id from devicetree instance + * + * This is equivalent to + * I3C_DEVICE_ID_DT(DT_DRV_INST(inst)). + * + * @param inst Devicetree instance number + */ +#define I3C_DEVICE_ID_DT_INST(inst) \ + I3C_DEVICE_ID_DT(DT_DRV_INST(inst)) + +/** + * @brief Structure initializer for i3c_device_desc from devicetree + * + * This helper macro expands to a static initializer for a struct + * i3c_device_desc by reading the relevant bus and device data + * from the devicetree. + * + * @param node_id Devicetree node identifier for the I3C device whose + * struct i3c_device_desc to create an initializer for + */ +#define I3C_DEVICE_DESC_DT(node_id) \ + { \ + .bus = DEVICE_DT_GET(DT_BUS(node_id)), \ + .dev = DEVICE_DT_GET(node_id), \ + .static_addr = DT_PROP_BY_IDX(node_id, reg, 0), \ + .pid = ((uint64_t)DT_PROP_BY_IDX(node_id, reg, 1) << 32)\ + | DT_PROP_BY_IDX(node_id, reg, 2), \ + .init_dynamic_addr = \ + DT_PROP_OR(node_id, assigned_address, 0), \ + } + +/** + * @brief Structure initializer for i3c_device_desc from devicetree instance + * + * This is equivalent to + * I3C_DEVICE_DESC_DT(DT_DRV_INST(inst)). + * + * @param inst Devicetree instance number + */ +#define I3C_DEVICE_DESC_DT_INST(inst) \ + I3C_DEVICE_DESC_DT(DT_DRV_INST(inst)) + +/** + * @brief Structure initializer for i3c_device_desc from devicetree + * + * This is mainly used by I3C_DEVICE_ARRAY_DT() to only + * create a struct if and only if it is an I3C device. + */ +#define I3C_DEVICE_DESC_DT_FILTERED(node_id) \ + COND_CODE_0(DT_PROP_BY_IDX(node_id, reg, 1), \ + (), (I3C_DEVICE_DESC_DT(node_id))) + +/** + * @brief Array initializer for a list of i3c_device_desc from devicetree + * + * This is a helper macro to generate an array for a list of i3c_device_desc + * from device tree. + * + * @param node_id Devicetree node identifier of the I3C controller + */ +#define I3C_DEVICE_ARRAY_DT(node_id) \ + { \ + DT_FOREACH_CHILD_STATUS_OKAY( \ + node_id, \ + I3C_DEVICE_DESC_DT_FILTERED) \ + } + +/** + * @brief Array initializer for a list of i3c_device_desc from devicetree instance + * + * This is equivalent to + * I3C_DEVICE_ARRAY_DT(DT_DRV_INST(inst)). + * + * @param inst Devicetree instance number of the I3C controller + */ +#define I3C_DEVICE_ARRAY_DT_INST(inst) \ + I3C_DEVICE_ARRAY_DT(DT_DRV_INST(inst)) + +/** + * @brief Like DEVICE_DT_DEFINE() with I3C target device specifics. + * + * Defines a I3C target device which implements the I3C target device API. + * + * @param node_id The devicetree node identifier. + * + * @param init_fn Name of the init function of the driver. + * + * @param pm_device PM device resources reference (NULL if device does not use PM). + * + * @param data_ptr Pointer to the device's private data. + * + * @param cfg_ptr The address to the structure containing the + * configuration information for this instance of the driver. + * + * @param level The initialization level. See SYS_INIT() for + * details. + * + * @param prio Priority within the selected initialization level. See + * SYS_INIT() for details. + * + * @param api_ptr Provides an initial pointer to the API function struct + * used by the driver. Can be NULL. + */ +#define I3C_DEVICE_DT_DEFINE(node_id, init_fn, pm_device, \ + data_ptr, cfg_ptr, level, prio, \ + api_ptr, ...) \ + DEVICE_DT_DEFINE(node_id, init_fn, pm_device, \ + data_ptr, cfg_ptr, level, prio, \ + api_ptr, __VA_ARGS__) + +/** + * @brief Like I3C_TARGET_DT_DEFINE() for an instance of a DT_DRV_COMPAT compatible + * + * @param inst instance number. This is replaced by + * DT_DRV_COMPAT(inst) in the call to I3C_TARGET_DT_DEFINE(). + * + * @param ... other parameters as expected by I3C_TARGET_DT_DEFINE(). + */ +#define I3C_DEVICE_DT_INST_DEFINE(inst, ...) \ + I3C_DEVICE_DT_DEFINE(DT_DRV_INST(inst), __VA_ARGS__) + +/** + * @brief Structure initializer for i3c_i2c_device_desc from devicetree + * + * This helper macro expands to a static initializer for a struct + * i3c_i2c_device_desc by reading the relevant bus and device data + * from the devicetree. + * + * @param node_id Devicetree node identifier for the I3C device whose + * struct i3c_i2c_device_desc to create an initializer for + */ +#define I3C_I2C_DEVICE_DESC_DT(node_id) \ + { \ + .bus = DEVICE_DT_GET(DT_BUS(node_id)), \ + .addr = DT_PROP_BY_IDX(node_id, reg, 0), \ + .lvr = DT_PROP_BY_IDX(node_id, reg, 2), \ + } + +/** + * @brief Structure initializer for i3c_i2c_device_desc from devicetree instance + * + * This is equivalent to + * I3C_I2C_DEVICE_DESC_DT(DT_DRV_INST(inst)). + * + * @param inst Devicetree instance number + */ +#define I3C_I2C_DEVICE_DESC_DT_INST(inst) \ + I3C_I2C_DEVICE_DESC_DT(DT_DRV_INST(inst)) + + +/** + * @brief Structure initializer for i3c_i2c_device_desc from devicetree + * + * This is mainly used by I3C_I2C_DEVICE_ARRAY_DT() to only + * create a struct if and only if it is an I2C device. + */ +#define I3C_I2C_DEVICE_DESC_DT_FILTERED(node_id) \ + COND_CODE_0(DT_PROP_BY_IDX(node_id, reg, 1), \ + (I3C_I2C_DEVICE_DESC_DT(node_id)), ()) + +/** + * @brief Array initializer for a list of i3c_i2c_device_desc from devicetree + * + * This is a helper macro to generate an array for a list of + * i3c_i2c_device_desc from device tree. + * + * @param node_id Devicetree node identifier of the I3C controller + */ +#define I3C_I2C_DEVICE_ARRAY_DT(node_id) \ + { \ + DT_FOREACH_CHILD_STATUS_OKAY( \ + node_id, \ + I3C_I2C_DEVICE_DESC_DT_FILTERED) \ + } + +/** + * @brief Array initializer for a list of i3c_i2c_device_desc from devicetree instance + * + * This is equivalent to + * I3C_I2C_DEVICE_ARRAY_DT(DT_DRV_INST(inst)). + * + * @param inst Devicetree instance number of the I3C controller + */ +#define I3C_I2C_DEVICE_ARRAY_DT_INST(inst) \ + I3C_I2C_DEVICE_ARRAY_DT(DT_DRV_INST(inst)) + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_DRIVERS_I3C_DEVICETREE_H_ */ diff --git a/include/zephyr/drivers/i3c/ibi.h b/include/zephyr/drivers/i3c/ibi.h new file mode 100644 index 00000000000..cd3c6db56de --- /dev/null +++ b/include/zephyr/drivers/i3c/ibi.h @@ -0,0 +1,107 @@ +/* + * Copyright 2022 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_I3C_IBI_H_ +#define ZEPHYR_INCLUDE_DRIVERS_I3C_IBI_H_ + +/** + * @brief I3C In-Band Interrupts + * @defgroup i3c_ibi I3C In-Band Interrupts + * @ingroup i3c_interface + * @{ + */ + +#include +#include +#include +#include + +#ifndef CONFIG_I3C_IBI_MAX_PAYLOAD_SIZE +#define CONFIG_I3C_IBI_MAX_PAYLOAD_SIZE 0 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct i3c_device_desc; + +/** + * @brief IBI Types. + */ +enum i3c_ibi_type { + /** Target interrupt */ + I3C_IBI_TARGET_INTR, + + /** Controller Role Request */ + I3C_IBI_CONTROLLER_ROLE_REQUEST, + + /** Hot Join Request */ + I3C_IBI_HOTJOIN, + + I3C_IBI_TYPE_MAX = I3C_IBI_HOTJOIN, +}; + +/** + * @brief Struct for IBI request. + */ +struct i3c_ibi { + /** Type of IBI. */ + enum i3c_ibi_type ibi_type; + + /** Pointer to payload of IBI. */ + uint8_t *payload; + + /** Length in bytes of the IBI payload. */ + uint8_t payload_len; +}; + +/** + * @brief Structure of payload buffer for IBI. + * + * This is used for the IBI callback. + */ +struct i3c_ibi_payload { + /** + * Length of available data in the payload buffer. + */ + uint8_t payload_len; + + /** + * Pointer to byte array as payload buffer. + */ + uint8_t payload[CONFIG_I3C_IBI_MAX_PAYLOAD_SIZE]; +}; + +/** + * @brief Function called when In-Band Interrupt received from target device. + * + * This function is invoked by the controller when the controller + * receives an In-Band Interrupt from the target device. + * + * A success return shall cause the controller to ACK the next byte + * received. An error return shall cause the controller to NACK the + * next byte received. + * + * @param target the device description structure associated with the + * device to which the operation is addressed. + * @param payload Payload associated with the IBI. NULL if there is + * no payload. + * + * @return 0 if the IBI is accepted, or a negative error code. + */ +typedef int (*i3c_target_ibi_cb_t)(struct i3c_device_desc *target, + struct i3c_ibi_payload *payload); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_DRIVERS_I3C_IBI_H_ */