| /* |
| * Copyright (c) 2022 Andes Technology Corporation. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/drivers/mbox.h> |
| |
| #define LOG_LEVEL CONFIG_MBOX_LOG_LEVEL |
| #include <zephyr/logging/log.h> |
| #include <zephyr/spinlock.h> |
| #include <zephyr/drivers/interrupt_controller/riscv_plic.h> |
| LOG_MODULE_REGISTER(mbox_andes_plic_sw); |
| |
| #define DT_DRV_COMPAT andestech_mbox_plic_sw |
| |
| struct mbox_plic_data { |
| mbox_callback_t *cb; |
| void **user_data; |
| struct k_spinlock lock; |
| }; |
| |
| struct mbox_plic_conf { |
| uint32_t channel_max; |
| const uint32_t *irq_sources; |
| }; |
| |
| static inline bool is_channel_valid(const struct device *dev, uint32_t ch) |
| { |
| const struct mbox_plic_conf *conf = dev->config; |
| |
| return (ch <= conf->channel_max) && conf->irq_sources[ch]; |
| } |
| |
| static int mbox_plic_send(const struct device *dev, uint32_t ch, const struct mbox_msg *msg) |
| { |
| const struct mbox_plic_conf *conf = dev->config; |
| |
| if (msg) { |
| LOG_WRN("Transfer mode is not supported"); |
| } |
| |
| if (!is_channel_valid(dev, ch)) { |
| return -EINVAL; |
| } |
| |
| /* Send the MBOX signal by setting the Pending bit register in the PLIC. */ |
| riscv_plic_irq_set_pending(conf->irq_sources[ch]); |
| |
| return 0; |
| } |
| |
| static int mbox_plic_register_callback(const struct device *dev, uint32_t ch, mbox_callback_t cb, |
| void *user_data) |
| { |
| struct mbox_plic_data *data = dev->data; |
| |
| if (!is_channel_valid(dev, ch)) { |
| return -EINVAL; |
| } |
| |
| k_spinlock_key_t key = k_spin_lock(&data->lock); |
| |
| data->cb[ch] = cb; |
| data->user_data[ch] = user_data; |
| |
| k_spin_unlock(&data->lock, key); |
| |
| return 0; |
| } |
| |
| static int mbox_plic_mtu_get(const struct device *dev) |
| { |
| /* MBOX PLIC only support signalling mode */ |
| return -ENOTSUP; |
| } |
| |
| static uint32_t mbox_plic_max_channels_get(const struct device *dev) |
| { |
| const struct mbox_plic_conf *conf = dev->config; |
| |
| return conf->channel_max; |
| } |
| |
| static int mbox_plic_set_enabled(const struct device *dev, uint32_t ch, bool enable) |
| { |
| struct mbox_plic_data *data = dev->data; |
| const struct mbox_plic_conf *conf = dev->config; |
| |
| if (!is_channel_valid(dev, ch)) { |
| return -EINVAL; |
| } |
| |
| if (enable && !(data->cb[ch])) { |
| LOG_WRN("Enabling channel without a registered callback\n"); |
| } |
| |
| if (enable) { |
| riscv_plic_irq_enable(conf->irq_sources[ch]); |
| } else { |
| riscv_plic_irq_disable(conf->irq_sources[ch]); |
| } |
| |
| return 0; |
| } |
| |
| static const struct mbox_driver_api mbox_plic_driver_api = { |
| .send = mbox_plic_send, |
| .register_callback = mbox_plic_register_callback, |
| .mtu_get = mbox_plic_mtu_get, |
| .max_channels_get = mbox_plic_max_channels_get, |
| .set_enabled = mbox_plic_set_enabled, |
| }; |
| |
| #define MBOX_PLIC_ISR_FUNCTION_IDX(node, prop, idx, n) \ |
| static void mbox_plic_irq_handler##n##_##idx(const struct device *dev) \ |
| { \ |
| struct mbox_plic_data *data = dev->data; \ |
| const uint32_t irq = DT_IRQ_BY_IDX(node, idx, irq); \ |
| if (data->cb[irq]) { \ |
| data->cb[irq](dev, irq, data->user_data[irq], NULL); \ |
| } \ |
| } |
| #define MBOX_PLIC_ISR_FUNCTION(n) \ |
| DT_INST_FOREACH_PROP_ELEM_SEP_VARGS(n, interrupt_names, MBOX_PLIC_ISR_FUNCTION_IDX, (), n) |
| #define MBOX_PLIC_IRQ_CONNECT_IDX(node, prop, idx, n) \ |
| IRQ_CONNECT(DT_IRQN_BY_IDX(node, idx), 1, mbox_plic_irq_handler##n##_##idx, \ |
| DEVICE_DT_INST_GET(n), 0) |
| #define MBOX_PLIC_IRQ_CONNECT(n) \ |
| DT_INST_FOREACH_PROP_ELEM_SEP_VARGS(n, interrupt_names, MBOX_PLIC_IRQ_CONNECT_IDX, (;), n) |
| #define MBOX_PLIC_INIT_FUNCTION(n) \ |
| static int mbox_plic_init##n(const struct device *dev) \ |
| { \ |
| MBOX_PLIC_IRQ_CONNECT(n); \ |
| return 0; \ |
| } |
| #define MBOX_PLIC_IRQ_SOURCE_IDX(node, prop, idx) \ |
| [DT_IRQ_BY_IDX(node, idx, irq)] = DT_IRQN_BY_IDX(node, idx) |
| #define MBOX_PLIC_IRQ_SOURCE(n) \ |
| static const unsigned int irq_sources##n[] = {DT_INST_FOREACH_PROP_ELEM_SEP( \ |
| n, interrupt_names, MBOX_PLIC_IRQ_SOURCE_IDX, (,))}; |
| #define MBOX_PLIC_DEVICE_INIT(n) \ |
| MBOX_PLIC_ISR_FUNCTION(n) \ |
| MBOX_PLIC_INIT_FUNCTION(n) \ |
| MBOX_PLIC_IRQ_SOURCE(n) \ |
| static mbox_callback_t mbox_callback##n[ARRAY_SIZE(irq_sources##n)]; \ |
| static void *user_data##n[ARRAY_SIZE(irq_sources##n)]; \ |
| static struct mbox_plic_data mbox_plic_data##n = { \ |
| .cb = mbox_callback##n, \ |
| .user_data = user_data##n, \ |
| }; \ |
| static const struct mbox_plic_conf mbox_plic_conf##n = { \ |
| .channel_max = ARRAY_SIZE(irq_sources##n), \ |
| .irq_sources = irq_sources##n, \ |
| }; \ |
| DEVICE_DT_INST_DEFINE(n, &mbox_plic_init##n, NULL, &mbox_plic_data##n, &mbox_plic_conf##n, \ |
| PRE_KERNEL_2, CONFIG_MBOX_INIT_PRIORITY, &mbox_plic_driver_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(MBOX_PLIC_DEVICE_INIT) |