From 51d9a71ebc86d955692c2d0463dd5effe52c0f52 Mon Sep 17 00:00:00 2001 From: Ryan McClelland Date: Thu, 12 Sep 2024 15:40:10 -0700 Subject: [PATCH] drivers: i3c: add controller handoff helper This adds controller handoff according to section 5.1.7.1 of the I3C specification. Signed-off-by: Ryan McClelland --- drivers/i3c/i3c_common.c | 120 +++++++++++++++++++++++++++++++ drivers/i3c/i3c_ibi_workq.c | 7 +- include/zephyr/drivers/i3c.h | 27 +++++++ include/zephyr/drivers/i3c/ccc.h | 7 +- 4 files changed, 157 insertions(+), 4 deletions(-) diff --git a/drivers/i3c/i3c_common.c b/drivers/i3c/i3c_common.c index c757b3ebd27ad2b..d29e9d6f3afcbb6 100644 --- a/drivers/i3c/i3c_common.c +++ b/drivers/i3c/i3c_common.c @@ -507,6 +507,126 @@ int i3c_dev_list_daa_addr_helper(struct i3c_addr_slots *addr_slots, return ret; } +uint8_t i3c_odd_parity(uint8_t p) +{ + p ^= p >> 4; + p &= 0xf; + return (0x9669 >> p) & 1; +} + +int i3c_device_controller_handoff(const struct i3c_device_desc *target, bool requested) +{ + int ret; + union i3c_ccc_getstatus status = {0}; + struct i3c_ccc_events i3c_events; + struct i3c_ccc_address handoff_address; + + /* Section 5.1.7.1 of I3C v1.1.1 Specification */ + + /* + * If the Active Controller intends to pass the Controller Role to a selected Secondary + * Controller that did not send a Controller Role Request, then the Active Controller should + * verify that the selected Secondary Controller is active and ready to respond to + * additional commands + */ + if (!requested) { + ret = i3c_ccc_do_getstatus_fmt1(target, &status); + if (ret != 0) { + return ret; + } + + if (I3C_CCC_GETSTATUS_ACTIVITY_MODE(status.fmt1.status) == + I3C_CCC_GETSTATUS_ACTIVITY_MODE_NCH) { + return -EBUSY; + } + } + + /* + * The Active Controller needs to disable Hot-Joins, Target Interrupt Requests, and other + * Bus events that could interfere with the Handoff, then it sends the appropriate + * Broadcast to disable those events before the Handoff. Once the Handoff is complete, the + * new Active Controller should re-enable events that are disabled in this step. + */ + i3c_events.events = I3C_CCC_EVT_ALL; + ret = i3c_ccc_do_events_all_set(target->bus, false, &i3c_events); + if (ret != 0) { + return ret; + } + + /** TODO: reconfigure MLANE if needed */ + + /* + * If the Active Controller knows that the selected Secondary Controller must be put into a + * different Activity State before Handoff, then the Active Controller shall send the + * appropriate Broadcast or Direct CCCs to put the Bus (or selected Devices) into a + * different Activity State + */ + if (target->crhdly1 & I3C_CCC_GETMXDS_CRDHLY1_SET_BUS_ACT_STATE) { + ret = i3c_ccc_do_entas( + target, I3C_CCC_GETMXDS_CRDHLY1_CTRL_HANDOFF_ACT_STATE(target->crhdly1)); + if (ret != 0) { + return ret; + } + } + + if ((target->getcaps.getcap3 & I3C_CCC_GETCAPS3_GETSTATUS_DEFINING_BYTE_SUPPORT) && + (target->crcaps.crcaps2 & I3C_CCC_GETCAPS_CRCAPS2_DEEP_SLEEP_CAPABLE)) { + ret = i3c_ccc_do_getstatus_fmt2(target, &status, GETSTATUS_FORMAT_2_PRECR); + if (ret != 0) { + return ret; + } + + /* + * If the Active Controller determines that the indicated Secondary Controller has + * been in a “deep sleep” state and may need to be re-synchronized with the most + * current list of I3C Targets and Group Addresses, then the Active Controller + * should send CCC DEFTGTS and DEFGRPA + */ + if (status.fmt2.precr & I3C_CCC_GETSTATUS_PRECR_DEEP_SLEEP_DETECTED) { + ret = i3c_bus_deftgts(target->bus); + if (ret != 0) { + return ret; + } + /* TODO: broadcast DEFGRPA when group address support comes */ + + /* Check CRCAPS if the device needs additional time to process */ + if (target->crcaps.crcaps2 & + I3C_CCC_GETCAPS_CRCAPS2_DELAYED_CONTROLLER_HANDOFF) { + /* + * Afterwards, the Active Controller should poll the Secondary + * Controller to ensure that it has successfully processed this data + * and indicates that it is ready to accept the Controller Role + */ + do { + ret = i3c_ccc_do_getstatus_fmt2(target, &status, + GETSTATUS_FORMAT_2_PRECR); + if (ret != 0) { + return ret; + } + } while (!(status.fmt2.precr & + I3C_CCC_GETSTATUS_PRECR_HANDOFF_DELAY_NACK)); + } + } + } + + /* + * After the Active Controller has prepared for Handoff, the Active Controller shall + * then issue a GETACCCR CCC + */ + ret = i3c_ccc_do_getacccr(target, &handoff_address); + if (ret != 0) { + return ret; + } + + /* Verify Odd Parity and Correct Dynamic Address Reply */ + if ((i3c_odd_parity(handoff_address.addr >> 1) != (handoff_address.addr & BIT(0))) || + (handoff_address.addr >> 1 != target->dynamic_addr)) { + return -EIO; + } + + return ret; +} + int i3c_device_basic_info_get(struct i3c_device_desc *target) { int ret; diff --git a/drivers/i3c/i3c_ibi_workq.c b/drivers/i3c/i3c_ibi_workq.c index 108f20f44e56971..339ae8e3c4ee5f7 100644 --- a/drivers/i3c/i3c_ibi_workq.c +++ b/drivers/i3c/i3c_ibi_workq.c @@ -194,8 +194,11 @@ static void i3c_ibi_work_handler(struct k_work *work) break; case I3C_IBI_CONTROLLER_ROLE_REQUEST: - /* TODO: Add support for controller role request */ - __fallthrough; + ret = i3c_device_controller_handoff(ibi_node->target, true); + if (ret != 0) { + LOG_ERR("i3c_device_controller_handoff returns %d", ret); + } + break; default: /* Unknown IBI type: do nothing */ diff --git a/include/zephyr/drivers/i3c.h b/include/zephyr/drivers/i3c.h index 55b05501011ceae..7537ca88d9e89ff 100644 --- a/include/zephyr/drivers/i3c.h +++ b/include/zephyr/drivers/i3c.h @@ -2146,6 +2146,33 @@ bool i3c_bus_has_sec_controller(const struct device *dev); */ int i3c_bus_deftgts(const struct device *dev); +/** + * @brief Calculate odd parity + * + * Calculate the Odd Parity of a Target Address. + * + * @param p The 7b target dynamic address + * + * @return The odd parity bit + */ +uint8_t i3c_odd_parity(uint8_t p); + +/** + * @brief Perform Controller Handoff + * + * This performs the controller handoff according to 5.1.7.1 of + * I3C v1.1.1 Specification. + * + * @param target I3C target device descriptor. + * @param requested True if the target requested the Handoff, False if + * the active controller is passing it to a secondary controller + * + * @retval 0 if successful. + * @retval -EIO General Input/Output error. + * @retval -EBUSY Target cannot accept Controller Handoff + */ +int i3c_device_controller_handoff(const struct i3c_device_desc *target, bool requested); + /* * This needs to be after declaration of struct i3c_driver_api, * or else compiler complains about undefined type inside diff --git a/include/zephyr/drivers/i3c/ccc.h b/include/zephyr/drivers/i3c/ccc.h index 525eeb75dec40b3..3125354436132bb 100644 --- a/include/zephyr/drivers/i3c/ccc.h +++ b/include/zephyr/drivers/i3c/ccc.h @@ -499,7 +499,7 @@ struct i3c_ccc_address { * @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, + * @note For SET GETACCCR, the address is left-shift by 1, * and bit[0] is the calculated odd parity bit. */ uint8_t addr; @@ -623,6 +623,9 @@ union i3c_ccc_getstatus { #define I3C_CCC_GETSTATUS_ACTIVITY_MODE(status) \ FIELD_GET(I3C_CCC_GETSTATUS_ACTIVITY_MODE_MASK, (status)) +/** GETSTATUS Format 1 - Activity Mode Unable to participate in Controller Handoff */ +#define I3C_CCC_GETSTATUS_ACTIVITY_MODE_NCH 0x3 + /** GETSTATUS Format 1 - Number of Pending Interrupts bitmask. */ #define I3C_CCC_GETSTATUS_NUM_INT_MASK GENMASK(3U, 0U) @@ -853,7 +856,7 @@ union i3c_ccc_getmxds { * @param crhdly1 GETMXDS value. */ #define I3C_CCC_GETMXDS_CRDHLY1_CTRL_HANDOFF_ACT_STATE(crhdly1) \ - FIELD_GET(I3C_CCC_GETMXDS_CRDHLY1_CTRL_HANDOFF_ACT_STATE_MASK, (chrdly1)) + FIELD_GET(I3C_CCC_GETMXDS_CRDHLY1_CTRL_HANDOFF_ACT_STATE_MASK, (crhdly1)) /** * @brief Indicate which format of GETCAPS to use.