| /* |
| * Copyright (c) 2016 Wind River Systems, Inc. |
| * Copyright (c) 2016,2022 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/kernel.h> |
| #include <zephyr/init.h> |
| #include <zephyr/drivers/i3c.h> |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_DECLARE(i3c, CONFIG_I3C_LOG_LEVEL); |
| |
| /* Statically allocated array of IBI work item nodes */ |
| static struct i3c_ibi_work i3c_ibi_work_nodes[CONFIG_I3C_IBI_WORKQUEUE_LENGTH]; |
| |
| static K_KERNEL_STACK_DEFINE(i3c_ibi_work_q_stack, |
| CONFIG_I3C_IBI_WORKQUEUE_STACK_SIZE); |
| |
| /* IBI workqueue */ |
| static struct k_work_q i3c_ibi_work_q; |
| |
| static sys_slist_t i3c_ibi_work_nodes_free; |
| |
| static inline int ibi_work_submit(struct i3c_ibi_work *ibi_node) |
| { |
| return k_work_submit_to_queue(&i3c_ibi_work_q, &ibi_node->work); |
| } |
| |
| int i3c_ibi_work_enqueue(struct i3c_ibi_work *ibi_work) |
| { |
| sys_snode_t *node; |
| struct i3c_ibi_work *ibi_node; |
| int ret; |
| |
| node = sys_slist_get(&i3c_ibi_work_nodes_free); |
| if (node == NULL) { |
| ret = -ENOMEM; |
| goto out; |
| } |
| |
| ibi_node = (struct i3c_ibi_work *)node; |
| |
| (void)memcpy(ibi_node, ibi_work, sizeof(*ibi_node)); |
| |
| ret = ibi_work_submit(ibi_node); |
| if (ret >= 0) { |
| ret = 0; |
| } |
| |
| out: |
| return ret; |
| } |
| |
| int i3c_ibi_work_enqueue_target_irq(struct i3c_device_desc *target, |
| uint8_t *payload, size_t payload_len) |
| { |
| sys_snode_t *node; |
| struct i3c_ibi_work *ibi_node; |
| int ret; |
| |
| node = sys_slist_get(&i3c_ibi_work_nodes_free); |
| if (node == NULL) { |
| ret = -ENOMEM; |
| goto out; |
| } |
| |
| ibi_node = (struct i3c_ibi_work *)node; |
| |
| ibi_node->type = I3C_IBI_TARGET_INTR; |
| ibi_node->target = target; |
| ibi_node->payload.payload_len = payload_len; |
| |
| if ((payload != NULL) && (payload_len > 0U)) { |
| (void)memcpy(&ibi_node->payload.payload[0], |
| payload, payload_len); |
| } |
| |
| ret = ibi_work_submit(ibi_node); |
| if (ret >= 0) { |
| ret = 0; |
| } |
| |
| out: |
| return ret; |
| } |
| |
| int i3c_ibi_work_enqueue_hotjoin(const struct device *dev) |
| { |
| sys_snode_t *node; |
| struct i3c_ibi_work *ibi_node; |
| int ret; |
| |
| node = sys_slist_get(&i3c_ibi_work_nodes_free); |
| if (node == NULL) { |
| ret = -ENOMEM; |
| goto out; |
| } |
| |
| ibi_node = (struct i3c_ibi_work *)node; |
| |
| ibi_node->type = I3C_IBI_HOTJOIN; |
| ibi_node->controller = dev; |
| ibi_node->payload.payload_len = 0; |
| |
| ret = ibi_work_submit(ibi_node); |
| if (ret >= 0) { |
| ret = 0; |
| } |
| |
| out: |
| return ret; |
| } |
| |
| int i3c_ibi_work_enqueue_cb(const struct device *dev, |
| k_work_handler_t work_cb) |
| { |
| sys_snode_t *node; |
| struct i3c_ibi_work *ibi_node; |
| int ret; |
| |
| node = sys_slist_get(&i3c_ibi_work_nodes_free); |
| if (node == NULL) { |
| ret = -ENOMEM; |
| goto out; |
| } |
| |
| ibi_node = (struct i3c_ibi_work *)node; |
| |
| ibi_node->type = I3C_IBI_WORKQUEUE_CB; |
| ibi_node->controller = dev; |
| ibi_node->work_cb = work_cb; |
| |
| ret = ibi_work_submit(ibi_node); |
| if (ret >= 0) { |
| ret = 0; |
| } |
| |
| out: |
| return ret; |
| } |
| |
| static void i3c_ibi_work_handler(struct k_work *work) |
| { |
| struct i3c_ibi_work *ibi_node = CONTAINER_OF(work, struct i3c_ibi_work, work); |
| struct i3c_ibi_payload *payload; |
| int ret = 0; |
| |
| if (IS_ENABLED(CONFIG_I3C_IBI_WORKQUEUE_VERBOSE_DEBUG) && |
| ((uint32_t)ibi_node->type <= I3C_IBI_TYPE_MAX)) { |
| LOG_DBG("Processing IBI work %p (type %d, len %u)", |
| ibi_node, (int)ibi_node->type, |
| ibi_node->payload.payload_len); |
| |
| if (ibi_node->payload.payload_len > 0U) { |
| LOG_HEXDUMP_DBG(&ibi_node->payload.payload[0], |
| ibi_node->payload.payload_len, "IBI Payload"); |
| } |
| } |
| |
| switch (ibi_node->type) { |
| case I3C_IBI_TARGET_INTR: |
| if (ibi_node->payload.payload_len != 0U) { |
| payload = &ibi_node->payload; |
| } else { |
| payload = NULL; |
| } |
| |
| ret = ibi_node->target->ibi_cb(ibi_node->target, payload); |
| if ((ret != 0) && (ret != -EBUSY)) { |
| LOG_ERR("IBI work %p cb returns %d", ibi_node, ret); |
| } |
| break; |
| |
| case I3C_IBI_HOTJOIN: |
| ret = i3c_do_daa(ibi_node->controller); |
| if ((ret != 0) && (ret != -EBUSY)) { |
| LOG_ERR("i3c_do_daa returns %d", ret); |
| } |
| break; |
| |
| case I3C_IBI_WORKQUEUE_CB: |
| if (ibi_node->work_cb != NULL) { |
| ibi_node->work_cb(work); |
| } |
| break; |
| |
| case I3C_IBI_CONTROLLER_ROLE_REQUEST: |
| /* TODO: Add support for controller role request */ |
| __fallthrough; |
| |
| default: |
| /* Unknown IBI type: do nothing */ |
| LOG_DBG("Cannot process IBI type %d", (int)ibi_node->type); |
| break; |
| } |
| |
| if (ret == -EBUSY) { |
| /* Retry if bus is busy. */ |
| if (ibi_work_submit(ibi_node) < 0) { |
| LOG_ERR("Error re-adding IBI work %p", ibi_node); |
| } |
| } else { |
| /* Add the now processed node back to the free list */ |
| sys_slist_append(&i3c_ibi_work_nodes_free, (sys_snode_t *)ibi_node); |
| } |
| } |
| |
| static int i3c_ibi_work_q_init(void) |
| { |
| struct k_work_queue_config cfg = { |
| .name = "i3c_ibi_workq", |
| .no_yield = true, |
| }; |
| |
| /* Init the linked list of work item nodes */ |
| sys_slist_init(&i3c_ibi_work_nodes_free); |
| |
| for (int i = 0; i < ARRAY_SIZE(i3c_ibi_work_nodes); i++) { |
| i3c_ibi_work_nodes[i].work.handler = i3c_ibi_work_handler; |
| |
| sys_slist_append(&i3c_ibi_work_nodes_free, |
| (sys_snode_t *)&i3c_ibi_work_nodes[i]); |
| } |
| |
| /* Start the workqueue */ |
| k_work_queue_start(&i3c_ibi_work_q, i3c_ibi_work_q_stack, |
| K_KERNEL_STACK_SIZEOF(i3c_ibi_work_q_stack), |
| CONFIG_I3C_IBI_WORKQUEUE_PRIORITY, &cfg); |
| |
| return 0; |
| } |
| |
| SYS_INIT(i3c_ibi_work_q_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); |