| /* |
| * Copyright (c) 2024 Felipe Neves. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT espressif_mbox_esp32 |
| #include "soc/dport_reg.h" |
| #include "soc/gpio_periph.h" |
| |
| #include <stdint.h> |
| #include <string.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/mbox.h> |
| #include <zephyr/drivers/interrupt_controller/intc_esp32.h> |
| #include <soc.h> |
| #include <zephyr/sys/atomic.h> |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(mbox_esp32, CONFIG_MBOX_LOG_LEVEL); |
| |
| #define ESP32_MBOX_LOCK_FREE_VAL 0xB33FFFFF |
| #define ESP32_MBOX_NOOP_VAL 0xFF |
| |
| __packed struct esp32_mbox_control { |
| uint16_t dest_cpu_msg_id[2]; |
| atomic_t lock; |
| }; |
| |
| struct esp32_mbox_memory { |
| volatile uint8_t *pro_cpu_shm; |
| volatile uint8_t *app_cpu_shm; |
| }; |
| |
| struct esp32_mbox_config { |
| int irq_source_pro_cpu; |
| int irq_priority_pro_cpu; |
| int irq_flags_pro_cpu; |
| int irq_source_app_cpu; |
| int irq_priority_app_cpu; |
| int irq_flags_app_cpu; |
| }; |
| |
| struct esp32_mbox_data { |
| mbox_callback_t cb; |
| void *user_data; |
| uint32_t this_core_id; |
| uint32_t other_core_id; |
| uint32_t shm_size; |
| struct esp32_mbox_memory shm; |
| struct esp32_mbox_control *control; |
| }; |
| |
| IRAM_ATTR static void esp32_mbox_isr(const struct device *dev) |
| { |
| struct esp32_mbox_data *dev_data = (struct esp32_mbox_data *)dev->data; |
| struct mbox_msg msg; |
| uint32_t core_id = dev_data->this_core_id; |
| |
| /* clear interrupt flag */ |
| if (core_id == 0) { |
| #if defined(CONFIG_SOC_SERIES_ESP32) |
| DPORT_WRITE_PERI_REG(DPORT_CPU_INTR_FROM_CPU_0_REG, 0); |
| #elif defined(CONFIG_SOC_SERIES_ESP32S3) |
| WRITE_PERI_REG(SYSTEM_CPU_INTR_FROM_CPU_0_REG, 0); |
| #endif |
| } else { |
| #if defined(CONFIG_SOC_SERIES_ESP32) |
| DPORT_WRITE_PERI_REG(DPORT_CPU_INTR_FROM_CPU_1_REG, 0); |
| #elif defined(CONFIG_SOC_SERIES_ESP32S3) |
| WRITE_PERI_REG(SYSTEM_CPU_INTR_FROM_CPU_1_REG, 0); |
| #endif |
| } |
| |
| /* first of all take the ownership of the shared memory */ |
| while (!atomic_cas(&dev_data->control->lock, ESP32_MBOX_LOCK_FREE_VAL, |
| dev_data->this_core_id)) |
| ; |
| |
| if (dev_data->cb) { |
| /* For ESP32 soft mbox driver, the message parameter of the callback holds |
| * the portion of shared memory that belongs to the current core ID. |
| */ |
| msg.data = (dev_data->this_core_id == 0) ? (const void *)dev_data->shm.pro_cpu_shm |
| : (const void *)dev_data->shm.app_cpu_shm; |
| msg.size = dev_data->shm_size; |
| |
| dev_data->cb(dev, dev_data->other_core_id, dev_data->user_data, &msg); |
| } |
| |
| /* unlock the shared memory */ |
| atomic_set(&dev_data->control->lock, ESP32_MBOX_LOCK_FREE_VAL); |
| } |
| |
| static int esp32_mbox_send(const struct device *dev, mbox_channel_id_t channel, |
| const struct mbox_msg *msg) |
| { |
| ARG_UNUSED(msg); |
| |
| struct esp32_mbox_data *dev_data = (struct esp32_mbox_data *)dev->data; |
| |
| if (channel > 0xFFFF) { |
| LOG_ERR("Invalid channel"); |
| return -EINVAL; |
| } |
| |
| uint32_t key = irq_lock(); |
| |
| /* try to lock the shared memory */ |
| while (!atomic_cas(&dev_data->control->lock, ESP32_MBOX_LOCK_FREE_VAL, |
| dev_data->this_core_id)) { |
| k_msleep(1); |
| } |
| |
| /* Only the lower 16bits of id are used */ |
| dev_data->control->dest_cpu_msg_id[dev_data->other_core_id] = (uint16_t)(channel & 0xFFFF); |
| |
| /* Generate interrupt in the remote core */ |
| if (dev_data->this_core_id == 0) { |
| atomic_set(&dev_data->control->lock, ESP32_MBOX_LOCK_FREE_VAL); |
| LOG_DBG("Generating interrupt on remote CPU 1 from CPU 0"); |
| #if defined(CONFIG_SOC_SERIES_ESP32) |
| DPORT_WRITE_PERI_REG(DPORT_CPU_INTR_FROM_CPU_1_REG, DPORT_CPU_INTR_FROM_CPU_1); |
| #elif defined(CONFIG_SOC_SERIES_ESP32S3) |
| WRITE_PERI_REG(SYSTEM_CPU_INTR_FROM_CPU_1_REG, SYSTEM_CPU_INTR_FROM_CPU_1); |
| #endif |
| |
| } else { |
| atomic_set(&dev_data->control->lock, ESP32_MBOX_LOCK_FREE_VAL); |
| LOG_DBG("Generating interrupt on remote CPU 0 from CPU 1"); |
| #if defined(CONFIG_SOC_SERIES_ESP32) |
| DPORT_WRITE_PERI_REG(DPORT_CPU_INTR_FROM_CPU_0_REG, DPORT_CPU_INTR_FROM_CPU_0); |
| #elif defined(CONFIG_SOC_SERIES_ESP32S3) |
| WRITE_PERI_REG(SYSTEM_CPU_INTR_FROM_CPU_0_REG, SYSTEM_CPU_INTR_FROM_CPU_0); |
| #endif |
| } |
| |
| irq_unlock(key); |
| |
| return 0; |
| } |
| |
| static int esp32_mbox_register_callback(const struct device *dev, mbox_channel_id_t channel, |
| mbox_callback_t cb, void *user_data) |
| { |
| ARG_UNUSED(channel); |
| |
| struct esp32_mbox_data *data = (struct esp32_mbox_data *)dev->data; |
| |
| if (!cb) { |
| LOG_ERR("Must provide callback"); |
| return -EINVAL; |
| } |
| |
| uint32_t key = irq_lock(); |
| |
| data->cb = cb; |
| data->user_data = user_data; |
| |
| irq_unlock(key); |
| |
| return 0; |
| } |
| |
| static int esp32_mbox_mtu_get(const struct device *dev) |
| { |
| struct esp32_mbox_data *data = (struct esp32_mbox_data *)dev->data; |
| |
| return data->shm_size; |
| } |
| |
| static uint32_t esp32_mbox_max_channels_get(const struct device *dev) |
| { |
| ARG_UNUSED(dev); |
| return 1; |
| } |
| |
| static int esp32_mbox_set_enabled(const struct device *dev, mbox_channel_id_t channel, bool enable) |
| { |
| /* The esp32 MBOX is always enabled |
| * but rpmsg backend needs MBOX set enabled to be |
| * implemented so just return success here |
| */ |
| |
| ARG_UNUSED(dev); |
| ARG_UNUSED(enable); |
| ARG_UNUSED(channel); |
| |
| return 0; |
| } |
| |
| static int esp32_mbox_init(const struct device *dev) |
| { |
| struct esp32_mbox_data *data = (struct esp32_mbox_data *)dev->data; |
| struct esp32_mbox_config *cfg = (struct esp32_mbox_config *)dev->config; |
| int ret; |
| |
| data->this_core_id = esp_core_id(); |
| data->other_core_id = (data->this_core_id == 0) ? 1 : 0; |
| |
| LOG_DBG("Size of MBOX shared memory: %d", data->shm_size); |
| LOG_DBG("Address of PRO_CPU MBOX shared memory: %p", data->shm.pro_cpu_shm); |
| LOG_DBG("Address of APP_CPU MBOX shared memory: %p", data->shm.app_cpu_shm); |
| LOG_DBG("Address of MBOX control structure: %p", data->control); |
| |
| /* pro_cpu is responsible to initialize the lock of shared memory */ |
| if (data->this_core_id == 0) { |
| ret = esp_intr_alloc(cfg->irq_source_pro_cpu, |
| ESP_PRIO_TO_FLAGS(cfg->irq_priority_pro_cpu) | |
| ESP_INT_FLAGS_CHECK(cfg->irq_flags_pro_cpu) | |
| ESP_INTR_FLAG_IRAM, |
| (intr_handler_t)esp32_mbox_isr, (void *)dev, NULL); |
| atomic_set(&data->control->lock, ESP32_MBOX_LOCK_FREE_VAL); |
| } else { |
| /* app_cpu wait for initialization from pro_cpu, then takes it, |
| * after that releases |
| */ |
| ret = esp_intr_alloc(cfg->irq_source_app_cpu, |
| ESP_PRIO_TO_FLAGS(cfg->irq_priority_app_cpu) | |
| ESP_INT_FLAGS_CHECK(cfg->irq_flags_app_cpu) | |
| ESP_INTR_FLAG_IRAM, |
| (intr_handler_t)esp32_mbox_isr, (void *)dev, NULL); |
| |
| LOG_DBG("Waiting CPU0 to sync"); |
| while (!atomic_cas(&data->control->lock, ESP32_MBOX_LOCK_FREE_VAL, |
| data->this_core_id)) |
| ; |
| |
| atomic_set(&data->control->lock, ESP32_MBOX_LOCK_FREE_VAL); |
| |
| LOG_DBG("Synchronization done"); |
| } |
| |
| return ret; |
| } |
| |
| static const struct mbox_driver_api esp32_mbox_driver_api = { |
| .send = esp32_mbox_send, |
| .register_callback = esp32_mbox_register_callback, |
| .mtu_get = esp32_mbox_mtu_get, |
| .max_channels_get = esp32_mbox_max_channels_get, |
| .set_enabled = esp32_mbox_set_enabled, |
| }; |
| |
| #define ESP32_MBOX_SHM_SIZE_BY_IDX(idx) DT_INST_PROP(idx, shared_memory_size) |
| |
| #define ESP32_MBOX_SHM_ADDR_BY_IDX(idx) DT_REG_ADDR(DT_PHANDLE(DT_DRV_INST(idx), shared_memory)) |
| |
| #define ESP32_MBOX_INIT(idx) \ |
| static struct esp32_mbox_config esp32_mbox_device_cfg_##idx = { \ |
| .irq_source_pro_cpu = DT_INST_IRQ_BY_IDX(idx, 0, irq), \ |
| .irq_priority_pro_cpu = DT_INST_IRQ_BY_IDX(idx, 0, priority), \ |
| .irq_flags_pro_cpu = DT_INST_IRQ_BY_IDX(idx, 0, flags), \ |
| .irq_source_app_cpu = DT_INST_IRQ_BY_IDX(idx, 1, irq), \ |
| .irq_priority_app_cpu = DT_INST_IRQ_BY_IDX(idx, 1, priority), \ |
| .irq_flags_app_cpu = DT_INST_IRQ_BY_IDX(idx, 1, flags), \ |
| }; \ |
| static struct esp32_mbox_data esp32_mbox_device_data_##idx = { \ |
| .shm_size = ESP32_MBOX_SHM_SIZE_BY_IDX(idx), \ |
| .shm.pro_cpu_shm = (uint8_t *)ESP32_MBOX_SHM_ADDR_BY_IDX(idx), \ |
| .shm.app_cpu_shm = (uint8_t *)ESP32_MBOX_SHM_ADDR_BY_IDX(idx) + \ |
| ESP32_MBOX_SHM_SIZE_BY_IDX(idx) / 2, \ |
| .control = (struct esp32_mbox_control *)DT_INST_REG_ADDR(idx), \ |
| }; \ |
| DEVICE_DT_INST_DEFINE(idx, &esp32_mbox_init, NULL, &esp32_mbox_device_data_##idx, \ |
| &esp32_mbox_device_cfg_##idx, PRE_KERNEL_2, \ |
| CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &esp32_mbox_driver_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(ESP32_MBOX_INIT); |