From 413c77cf4e3a35fa57af031cbbdf9ebef40486f8 Mon Sep 17 00:00:00 2001 From: Laurentiu Mihalcea Date: Wed, 7 Aug 2024 11:04:19 +0300 Subject: [PATCH] firmware: introduce SCMI core support Introduce core support for ARM's SCMI (System Control and Management Interface). This includes: * shared memory (SHMEM) driver. This consists of a suite of functions used to interact with the shared memory area. * shared memory and doorbell-based transport layer driver. Data is passed between platform and agent via shared memory. Signaling is done using polling (PRE_KERNEL) and doorbells (POST_KERNEL). This makes use of Zephyr MBOX API (for signaling purposes) and the SHMEM driver (for polling and data transfer). * core driver - acts as glue between transport and protocol layers. Provides synchronized access to transport layer channels and channel assignment/initialization. * infrastructure for creating SCMI protocols This is based on ARM's SCMI Platform Design Document: DEN0056E. Signed-off-by: Laurentiu Mihalcea --- cmake/linker_script/common/common-ram.cmake | 4 + drivers/CMakeLists.txt | 1 + drivers/Kconfig | 1 + drivers/firmware/CMakeLists.txt | 3 + drivers/firmware/Kconfig | 15 + drivers/firmware/scmi/CMakeLists.txt | 8 + drivers/firmware/scmi/Kconfig | 45 +++ drivers/firmware/scmi/core.c | 214 +++++++++++++ drivers/firmware/scmi/mailbox.c | 115 +++++++ drivers/firmware/scmi/mailbox.h | 117 ++++++++ drivers/firmware/scmi/shmem.c | 201 +++++++++++++ dts/bindings/firmware/arm,scmi-shmem.yaml | 12 + dts/bindings/firmware/arm,scmi.yaml | 68 +++++ .../zephyr/drivers/firmware/scmi/protocol.h | 122 ++++++++ include/zephyr/drivers/firmware/scmi/shmem.h | 67 +++++ .../zephyr/drivers/firmware/scmi/transport.h | 274 +++++++++++++++++ include/zephyr/drivers/firmware/scmi/util.h | 280 ++++++++++++++++++ include/zephyr/linker/common-ram.ld | 4 + 18 files changed, 1551 insertions(+) create mode 100644 drivers/firmware/CMakeLists.txt create mode 100644 drivers/firmware/Kconfig create mode 100644 drivers/firmware/scmi/CMakeLists.txt create mode 100644 drivers/firmware/scmi/Kconfig create mode 100644 drivers/firmware/scmi/core.c create mode 100644 drivers/firmware/scmi/mailbox.c create mode 100644 drivers/firmware/scmi/mailbox.h create mode 100644 drivers/firmware/scmi/shmem.c create mode 100644 dts/bindings/firmware/arm,scmi-shmem.yaml create mode 100644 dts/bindings/firmware/arm,scmi.yaml create mode 100644 include/zephyr/drivers/firmware/scmi/protocol.h create mode 100644 include/zephyr/drivers/firmware/scmi/shmem.h create mode 100644 include/zephyr/drivers/firmware/scmi/transport.h create mode 100644 include/zephyr/drivers/firmware/scmi/util.h diff --git a/cmake/linker_script/common/common-ram.cmake b/cmake/linker_script/common/common-ram.cmake index 6a3dd4080ef..091da6ff541 100644 --- a/cmake/linker_script/common/common-ram.cmake +++ b/cmake/linker_script/common/common-ram.cmake @@ -55,6 +55,10 @@ if(CONFIG_NETWORKING) zephyr_iterable_section(NAME eth_bridge GROUP DATA_REGION ${XIP_ALIGN_WITH_INPUT} SUBALIGN CONFIG_LINKER_ITERABLE_SUBALIGN) endif() +if(CONFIG_ARM_SCMI) + zephyr_iterable_section(NAME scmi_protocol GROUP DATA_REGION ${XIP_ALIGN_WITH_INPUT} SUBALIGN CONFIG_LINKER_ITERABLE_SUBALIGN) +endif() + if(CONFIG_SENSING) zephyr_iterable_section(NAME sensing_sensor GROUP DATA_REGION ${XIP_ALIGN_WITH_INPUT} SUBALIGN CONFIG_LINKER_ITERABLE_SUBALIGN) endif() diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index a0c7d2cb88f..0f645e98520 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -7,6 +7,7 @@ add_definitions(-D__ZEPHYR_SUPERVISOR__) # zephyr-keep-sorted-start add_subdirectory(disk) +add_subdirectory(firmware) add_subdirectory(interrupt_controller) add_subdirectory(misc) add_subdirectory(pcie) diff --git a/drivers/Kconfig b/drivers/Kconfig index cfa8d08b6a2..a11ede616da 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -30,6 +30,7 @@ source "drivers/eeprom/Kconfig" source "drivers/entropy/Kconfig" source "drivers/espi/Kconfig" source "drivers/ethernet/Kconfig" +source "drivers/firmware/Kconfig" source "drivers/flash/Kconfig" source "drivers/fpga/Kconfig" source "drivers/fuel_gauge/Kconfig" diff --git a/drivers/firmware/CMakeLists.txt b/drivers/firmware/CMakeLists.txt new file mode 100644 index 00000000000..1222e9388ea --- /dev/null +++ b/drivers/firmware/CMakeLists.txt @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: Apache-2.0 + +add_subdirectory_ifdef(CONFIG_ARM_SCMI scmi) diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig new file mode 100644 index 00000000000..41576061237 --- /dev/null +++ b/drivers/firmware/Kconfig @@ -0,0 +1,15 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +menu "Firmware drivers" + +config ARM_SCMI + bool "Support for ARM's SCMI" + depends on ARM || ARM64 + help + Enable support for ARM's System Configuration and Management + Interface (SCMI). + +source "drivers/firmware/scmi/Kconfig" + +endmenu diff --git a/drivers/firmware/scmi/CMakeLists.txt b/drivers/firmware/scmi/CMakeLists.txt new file mode 100644 index 00000000000..09febb5d721 --- /dev/null +++ b/drivers/firmware/scmi/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +# SCMI core files +zephyr_library_sources_ifdef(CONFIG_ARM_SCMI core.c) +zephyr_library_sources_ifdef(CONFIG_ARM_SCMI_MAILBOX_TRANSPORT mailbox.c) +zephyr_library_sources_ifdef(CONFIG_ARM_SCMI_SHMEM shmem.c) diff --git a/drivers/firmware/scmi/Kconfig b/drivers/firmware/scmi/Kconfig new file mode 100644 index 00000000000..924dd3a8ded --- /dev/null +++ b/drivers/firmware/scmi/Kconfig @@ -0,0 +1,45 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +if ARM_SCMI + +config ARM_SCMI_MAILBOX_TRANSPORT + bool "SCMI transport based on shared memory and doorbells" + default y + depends on DT_HAS_ARM_SCMI_ENABLED + depends on ARM_SCMI_SHMEM + select ARM_SCMI_TRANSPORT_HAS_STATIC_CHANNELS + help + Enable support for SCMI transport based on shared memory + and doorbells. + +config ARM_SCMI_SHMEM + bool "SCMI shared memory (SHMEM) driver" + default y + depends on DT_HAS_ARM_SCMI_SHMEM_ENABLED + help + Enable support for SCMI shared memory (SHMEM) driver. + +config ARM_SCMI_SHMEM_INIT_PRIORITY + int "SCMI shared memory (SHMEM) initialization priority" + default 15 + help + SCMI SHMEM driver device initialization priority. + +config ARM_SCMI_TRANSPORT_HAS_STATIC_CHANNELS + bool "Transport layer has static channels" + help + Enable this if the SCMI transport layer uses static channels. + What this means is that each protocol will have its channels + assigned at compile time. This option is recommended for + transport layer drivers which can use the default channel + allocation scheme (i.e: use protocol-specific channels if + they exist, otherwise use base protocol channels). + +config ARM_SCMI_TRANSPORT_INIT_PRIORITY + int "SCMI transport layer initialization priority" + default 20 + help + SCMI transport driver device initialization priority. + +endif # ARM_SCMI diff --git a/drivers/firmware/scmi/core.c b/drivers/firmware/scmi/core.c new file mode 100644 index 00000000000..58e354485ee --- /dev/null +++ b/drivers/firmware/scmi/core.c @@ -0,0 +1,214 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +LOG_MODULE_REGISTER(scmi_core); + +#define SCMI_CHAN_LOCK_TIMEOUT_USEC 500 +#define SCMI_CHAN_SEM_TIMEOUT_USEC 500 + +int scmi_status_to_errno(int scmi_status) +{ + switch (scmi_status) { + case SCMI_SUCCESS: + return 0; + case SCMI_NOT_SUPPORTED: + return -EOPNOTSUPP; + case SCMI_INVALID_PARAMETERS: + return -EINVAL; + case SCMI_DENIED: + return -EACCES; + case SCMI_NOT_FOUND: + return -ENOENT; + case SCMI_OUT_OF_RANGE: + return -ERANGE; + case SCMI_IN_USE: + case SCMI_BUSY: + return -EBUSY; + case SCMI_PROTOCOL_ERROR: + return -EPROTO; + case SCMI_COMMS_ERROR: + case SCMI_GENERIC_ERROR: + case SCMI_HARDWARE_ERROR: + default: + return -EIO; + } +} + +static void scmi_core_reply_cb(struct scmi_channel *chan) +{ + if (!k_is_pre_kernel()) { + k_sem_give(&chan->sem); + } +} + +static int scmi_core_setup_chan(const struct device *transport, + struct scmi_channel *chan, bool tx) +{ + int ret; + + if (!chan) { + return -EINVAL; + } + + if (chan->ready) { + return 0; + } + + /* no support for RX channels ATM */ + if (!tx) { + return -ENOTSUP; + } + + k_mutex_init(&chan->lock); + k_sem_init(&chan->sem, 0, 1); + + chan->cb = scmi_core_reply_cb; + + /* setup transport-related channel data */ + ret = scmi_transport_setup_chan(transport, chan, tx); + if (ret < 0) { + LOG_ERR("failed to setup channel"); + return ret; + } + + /* protocols might share a channel. In such cases, this + * will stop them from being initialized again. + */ + chan->ready = true; + + return 0; +} + +static int scmi_send_message_pre_kernel(struct scmi_protocol *proto, + struct scmi_message *msg, + struct scmi_message *reply) +{ + int ret; + + ret = scmi_transport_send_message(proto->transport, proto->tx, msg); + if (ret < 0) { + return ret; + } + + /* no kernel primitives, we're forced to poll here. + * + * Cortex-M quirk: no interrupts at this point => no timer => + * no timeout mechanism => this can block the whole system. + * + * TODO: is there a better way to handle this? + */ + while (!scmi_transport_channel_is_free(proto->transport, proto->tx)) { + } + + ret = scmi_transport_read_message(proto->transport, proto->tx, reply); + if (ret < 0) { + return ret; + } + + return ret; +} + +static int scmi_send_message_post_kernel(struct scmi_protocol *proto, + struct scmi_message *msg, + struct scmi_message *reply) +{ + int ret = 0; + + if (!proto->tx) { + return -ENODEV; + } + + /* wait for channel to be free */ + ret = k_mutex_lock(&proto->tx->lock, K_USEC(SCMI_CHAN_LOCK_TIMEOUT_USEC)); + if (ret < 0) { + LOG_ERR("failed to acquire chan lock"); + return ret; + } + + ret = scmi_transport_send_message(proto->transport, proto->tx, msg); + if (ret < 0) { + LOG_ERR("failed to send message"); + goto out_release_mutex; + } + + /* only one protocol instance can wait for a message reply at a time */ + ret = k_sem_take(&proto->tx->sem, K_USEC(SCMI_CHAN_SEM_TIMEOUT_USEC)); + if (ret < 0) { + LOG_ERR("failed to wait for msg reply"); + goto out_release_mutex; + } + + ret = scmi_transport_read_message(proto->transport, proto->tx, reply); + if (ret < 0) { + LOG_ERR("failed to read reply"); + goto out_release_mutex; + } + +out_release_mutex: + k_mutex_unlock(&proto->tx->lock); + + return ret; +} + +int scmi_send_message(struct scmi_protocol *proto, struct scmi_message *msg, + struct scmi_message *reply) +{ + if (!proto->tx) { + return -ENODEV; + } + + if (!proto->tx->ready) { + return -EINVAL; + } + + if (k_is_pre_kernel()) { + return scmi_send_message_pre_kernel(proto, msg, reply); + } else { + return scmi_send_message_post_kernel(proto, msg, reply); + } +} + +static int scmi_core_protocol_setup(const struct device *transport) +{ + int ret; + + STRUCT_SECTION_FOREACH(scmi_protocol, it) { + it->transport = transport; + +#ifndef CONFIG_ARM_SCMI_TRANSPORT_HAS_STATIC_CHANNELS + /* no static channel allocation, attempt dynamic binding */ + it->tx = scmi_transport_request_channel(transport, it->id, true); +#endif /* CONFIG_ARM_SCMI_TRANSPORT_HAS_STATIC_CHANNELS */ + + if (!it->tx) { + return -ENODEV; + } + + ret = scmi_core_setup_chan(transport, it->tx, true); + if (ret < 0) { + return ret; + } + } + + return 0; +} + +int scmi_core_transport_init(const struct device *transport) +{ + int ret; + + ret = scmi_transport_init(transport); + if (ret < 0) { + return ret; + } + + return scmi_core_protocol_setup(transport); +} diff --git a/drivers/firmware/scmi/mailbox.c b/drivers/firmware/scmi/mailbox.c new file mode 100644 index 00000000000..2c61afa9a87 --- /dev/null +++ b/drivers/firmware/scmi/mailbox.c @@ -0,0 +1,115 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "mailbox.h" + +LOG_MODULE_REGISTER(scmi_mbox); + +static void scmi_mbox_cb(const struct device *mbox, + mbox_channel_id_t channel_id, + void *user_data, + struct mbox_msg *data) +{ + struct scmi_channel *scmi_chan = user_data; + + if (scmi_chan->cb) + scmi_chan->cb(scmi_chan); +} + +static int scmi_mbox_send_message(const struct device *transport, + struct scmi_channel *chan, + struct scmi_message *msg) +{ + struct scmi_mbox_channel *mbox_chan; + int ret; + + mbox_chan = chan->data; + + ret = scmi_shmem_write_message(mbox_chan->shmem, msg); + if (ret < 0) { + LOG_ERR("failed to write message to shmem: %d", ret); + return ret; + } + + ret = mbox_send_dt(&mbox_chan->tx, NULL); + if (ret < 0) { + LOG_ERR("failed to ring doorbell: %d", ret); + return ret; + } + + return 0; +} + +static int scmi_mbox_read_message(const struct device *transport, + struct scmi_channel *chan, + struct scmi_message *msg) +{ + struct scmi_mbox_channel *mbox_chan; + + mbox_chan = chan->data; + + return scmi_shmem_read_message(mbox_chan->shmem, msg); +} + +static bool scmi_mbox_channel_is_free(const struct device *transport, + struct scmi_channel *chan) +{ + struct scmi_mbox_channel *mbox_chan = chan->data; + + return scmi_shmem_channel_status(mbox_chan->shmem) & + SCMI_SHMEM_CHAN_STATUS_BUSY_BIT; +} + +static int scmi_mbox_setup_chan(const struct device *transport, + struct scmi_channel *chan, + bool tx) +{ + int ret; + struct scmi_mbox_channel *mbox_chan; + struct mbox_dt_spec *tx_reply; + + mbox_chan = chan->data; + + if (!tx) { + return -ENOTSUP; + } + + if (mbox_chan->tx_reply.dev) { + tx_reply = &mbox_chan->tx_reply; + } else { + tx_reply = &mbox_chan->tx; + } + + ret = mbox_register_callback_dt(tx_reply, scmi_mbox_cb, chan); + if (ret < 0) { + LOG_ERR("failed to register tx reply cb"); + return ret; + } + + ret = mbox_set_enabled_dt(tx_reply, true); + if (ret < 0) { + LOG_ERR("failed to enable tx reply dbell"); + } + + /* enable interrupt-based communication */ + scmi_shmem_update_flags(mbox_chan->shmem, + SCMI_SHMEM_CHAN_FLAG_IRQ_BIT, + SCMI_SHMEM_CHAN_FLAG_IRQ_BIT); + + return 0; +} + +static struct scmi_transport_api scmi_mbox_api = { + .setup_chan = scmi_mbox_setup_chan, + .send_message = scmi_mbox_send_message, + .read_message = scmi_mbox_read_message, + .channel_is_free = scmi_mbox_channel_is_free, +}; + +DT_INST_SCMI_MAILBOX_DEFINE(0, PRE_KERNEL_1, + CONFIG_ARM_SCMI_TRANSPORT_INIT_PRIORITY, + &scmi_mbox_api); diff --git a/drivers/firmware/scmi/mailbox.h b/drivers/firmware/scmi/mailbox.h new file mode 100644 index 00000000000..4e758d8e298 --- /dev/null +++ b/drivers/firmware/scmi/mailbox.h @@ -0,0 +1,117 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _ZEPHYR_DRIVERS_FIRMWARE_SCMI_MAILBOX_H_ +#define _ZEPHYR_DRIVERS_FIRMWARE_SCMI_MAILBOX_H_ + +#include +#include +#include +#include +#include + +#define DT_DRV_COMPAT arm_scmi + +/* get a `struct device` for a protocol's shared memory area */ +#define _SCMI_MBOX_SHMEM_BY_IDX(node_id, idx) \ + COND_CODE_1(DT_PROP_HAS_IDX(node_id, shmem, idx), \ + (DEVICE_DT_GET(DT_PROP_BY_IDX(node_id, shmem, idx))), \ + (NULL)) + +/* get the name of mailbox channel's private data */ +#define _SCMI_MBOX_CHAN_NAME(proto, idx)\ + CONCAT(SCMI_TRANSPORT_CHAN_NAME(proto, idx), _, priv) + +/* fetch a mailbox channel's doorbell */ +#define _SCMI_MBOX_CHAN_DBELL(node_id, name) \ + COND_CODE_1(DT_PROP_HAS_NAME(node_id, mboxes, name), \ + (MBOX_DT_SPEC_GET(node_id, name)), \ + ({ })) + +/* define private data for a protocol TX channel */ +#define _SCMI_MBOX_CHAN_DEFINE_PRIV_TX(node_id, proto) \ + static struct scmi_mbox_channel _SCMI_MBOX_CHAN_NAME(proto, 0) =\ + { \ + .shmem = _SCMI_MBOX_SHMEM_BY_IDX(node_id, 0), \ + .tx = _SCMI_MBOX_CHAN_DBELL(node_id, tx), \ + .tx_reply = _SCMI_MBOX_CHAN_DBELL(node_id, tx_reply), \ + } + +/* + * Define a mailbox channel. This does two things: + * 1) Define the mandatory `struct scmi_channel` structure + * 2) Define the mailbox-specific private data for said + * channel (i.e: a struct scmi_mbox_channel) + */ +#define _SCMI_MBOX_CHAN_DEFINE(node_id, proto, idx) \ + _SCMI_MBOX_CHAN_DEFINE_PRIV_TX(node_id, proto); \ + DT_SCMI_TRANSPORT_CHAN_DEFINE(node_id, idx, proto, \ + &(_SCMI_MBOX_CHAN_NAME(proto, idx))); \ + +/* + * Optionally define a mailbox channel for a protocol. This is optional + * because a protocol might not have a dedicated channel. + */ +#define _SCMI_MBOX_CHAN_DEFINE_OPTIONAL(node_id, proto, idx) \ + COND_CODE_1(DT_PROP_HAS_IDX(node_id, shmem, idx), \ + (_SCMI_MBOX_CHAN_DEFINE(node_id, proto, idx)), \ + ()) + +/* define a TX channel for a protocol node. This is preferred over + * _SCMI_MBOX_CHAN_DEFINE_OPTIONAL() since support for RX channels + * might be added later on. This macro is supposed to also define + * the RX channel + */ +#define SCMI_MBOX_PROTO_CHAN_DEFINE(node_id)\ + _SCMI_MBOX_CHAN_DEFINE_OPTIONAL(node_id, DT_REG_ADDR(node_id), 0) + +/* define and validate base protocol TX channel */ +#define DT_INST_SCMI_MBOX_BASE_CHAN_DEFINE(inst) \ + BUILD_ASSERT(DT_INST_PROP_LEN(inst, mboxes) != 1 || \ + (DT_INST_PROP_HAS_IDX(inst, shmem, 0) && \ + DT_INST_PROP_HAS_NAME(inst, mboxes, tx)), \ + "bad bidirectional channel description"); \ + \ + BUILD_ASSERT(DT_INST_PROP_LEN(inst, mboxes) != 2 || \ + (DT_INST_PROP_HAS_NAME(inst, mboxes, tx) && \ + DT_INST_PROP_HAS_NAME(inst, mboxes, tx_reply)), \ + "bad unidirectional channel description"); \ + \ + BUILD_ASSERT(DT_INST_PROP_LEN(inst, shmem) == 1, \ + "bad SHMEM count"); \ + \ + BUILD_ASSERT(DT_INST_PROP_LEN(inst, mboxes) <= 2, \ + "bad mbox count"); \ + \ + _SCMI_MBOX_CHAN_DEFINE(DT_INST(inst, DT_DRV_COMPAT), SCMI_PROTOCOL_BASE, 0) + +/* + * Define the mailbox-based transport layer. What this does is: + * + * 1) Goes through all protocol nodes (children of the `scmi` node) + * and creates a `struct scmi_channel` and its associated + * `struct scmi_mbox_channel` if the protocol has a dedicated channel. + * + * 2) Creates aforementioned structures for the base protocol + * (identified by the `scmi` node) + * + * 3) "registers" the driver via `DT_INST_SCMI_TRANSPORT_DEFINE()`. + */ +#define DT_INST_SCMI_MAILBOX_DEFINE(inst, level, prio, api) \ + DT_INST_FOREACH_CHILD_STATUS_OKAY(inst, SCMI_MBOX_PROTO_CHAN_DEFINE) \ + DT_INST_SCMI_MBOX_BASE_CHAN_DEFINE(inst) \ + DT_INST_SCMI_TRANSPORT_DEFINE(inst, NULL, NULL, NULL, level, prio, api) + +struct scmi_mbox_channel { + /* SHMEM area bound to the channel */ + const struct device *shmem; + /* TX dbell */ + struct mbox_dt_spec tx; + /* TX reply dbell */ + struct mbox_dt_spec tx_reply; +}; + +#endif /* _ZEPHYR_DRIVERS_FIRMWARE_SCMI_MAILBOX_H_ */ diff --git a/drivers/firmware/scmi/shmem.c b/drivers/firmware/scmi/shmem.c new file mode 100644 index 00000000000..6ec645cbc30 --- /dev/null +++ b/drivers/firmware/scmi/shmem.c @@ -0,0 +1,201 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +LOG_MODULE_REGISTER(arm_scmi_shmem); + +#define DT_DRV_COMPAT arm_scmi_shmem + +#ifndef DEVICE_MMIO_IS_IN_RAM +#define device_map(virt, phys, size, flags) *(virt) = (phys) +#endif /* DEVICE_MMIO_IS_IN_RAM */ + +struct scmi_shmem_config { + uintptr_t phys_addr; + uint32_t size; +}; + +struct scmi_shmem_data { + mm_reg_t regmap; +}; + +struct scmi_shmem_layout { + volatile uint32_t res0; + volatile uint32_t chan_status; + volatile uint32_t res1[2]; + volatile uint32_t chan_flags; + volatile uint32_t len; + volatile uint32_t msg_hdr; +}; + +int scmi_shmem_get_channel_status(const struct device *dev, uint32_t *status) +{ + struct scmi_shmem_data *data; + struct scmi_shmem_layout *layout; + + data = dev->data; + layout = (struct scmi_shmem_layout *)data->regmap; + + *status = layout->chan_status; + + return 0; +} + +static void scmi_shmem_memcpy(mm_reg_t dst, mm_reg_t src, uint32_t bytes) +{ + int i; + + for (i = 0; i < bytes; i++) { + sys_write8(*(uint8_t *)(src + i), dst + i); + } +} + +int scmi_shmem_read_message(const struct device *shmem, struct scmi_message *msg) +{ + struct scmi_shmem_layout *layout; + struct scmi_shmem_data *data; + const struct scmi_shmem_config *cfg; + + data = shmem->data; + cfg = shmem->config; + layout = (struct scmi_shmem_layout *)data->regmap; + + /* some sanity checks first */ + if (!msg) { + return -EINVAL; + } + + if (!msg->content && msg->len) { + return -EINVAL; + } + + if (cfg->size < (sizeof(*layout) + msg->len)) { + LOG_ERR("message doesn't fit in shmem area"); + return -EINVAL; + } + + /* mismatch between expected reply size and actual size? */ + if (msg->len != (layout->len - sizeof(layout->msg_hdr))) { + LOG_ERR("bad message len. Expected 0x%x, got 0x%x", + msg->len, + (uint32_t)(layout->len - sizeof(layout->msg_hdr))); + return -EINVAL; + } + + /* header match? */ + if (layout->msg_hdr != msg->hdr) { + LOG_ERR("bad message header. Expected 0x%x, got 0x%x", + msg->hdr, layout->msg_hdr); + return -EINVAL; + } + + if (msg->content) { + scmi_shmem_memcpy(POINTER_TO_UINT(msg->content), + data->regmap + sizeof(*layout), msg->len); + } + + return 0; +} + +int scmi_shmem_write_message(const struct device *shmem, struct scmi_message *msg) +{ + struct scmi_shmem_layout *layout; + struct scmi_shmem_data *data; + const struct scmi_shmem_config *cfg; + + data = shmem->data; + cfg = shmem->config; + layout = (struct scmi_shmem_layout *)data->regmap; + + /* some sanity checks first */ + if (!msg) { + return -EINVAL; + } + + if (!msg->content && msg->len) { + return -EINVAL; + } + + if (cfg->size < (sizeof(*layout) + msg->len)) { + return -EINVAL; + } + + if (!(layout->chan_status & SCMI_SHMEM_CHAN_STATUS_BUSY_BIT)) { + return -EBUSY; + } + + layout->len = sizeof(layout->msg_hdr) + msg->len; + layout->msg_hdr = msg->hdr; + + if (msg->content) { + scmi_shmem_memcpy(data->regmap + sizeof(*layout), + POINTER_TO_UINT(msg->content), msg->len); + } + + /* done, mark channel as busy and proceed */ + layout->chan_status &= ~SCMI_SHMEM_CHAN_STATUS_BUSY_BIT; + + return 0; +} + +uint32_t scmi_shmem_channel_status(const struct device *shmem) +{ + struct scmi_shmem_layout *layout; + struct scmi_shmem_data *data; + + data = shmem->data; + layout = (struct scmi_shmem_layout *)data->regmap; + + return layout->chan_status; +} + +void scmi_shmem_update_flags(const struct device *shmem, uint32_t mask, uint32_t val) +{ + struct scmi_shmem_layout *layout; + struct scmi_shmem_data *data; + + data = shmem->data; + layout = (struct scmi_shmem_layout *)data->regmap; + + layout->chan_flags = (layout->chan_flags & ~mask) | (val & mask); +} + +static int scmi_shmem_init(const struct device *dev) +{ + const struct scmi_shmem_config *cfg; + struct scmi_shmem_data *data; + + cfg = dev->config; + data = dev->data; + + if (cfg->size < sizeof(struct scmi_shmem_layout)) { + return -EINVAL; + } + + device_map(&data->regmap, cfg->phys_addr, cfg->size, K_MEM_CACHE_NONE); + + return 0; +} + +#define SCMI_SHMEM_INIT(inst) \ +static const struct scmi_shmem_config config_##inst = { \ + .phys_addr = DT_INST_REG_ADDR(inst), \ + .size = DT_INST_REG_SIZE(inst), \ +}; \ + \ +static struct scmi_shmem_data data_##inst; \ + \ +DEVICE_DT_INST_DEFINE(inst, &scmi_shmem_init, NULL, \ + &data_##inst, &config_##inst, \ + PRE_KERNEL_1, \ + CONFIG_ARM_SCMI_SHMEM_INIT_PRIORITY, \ + NULL); + +DT_INST_FOREACH_STATUS_OKAY(SCMI_SHMEM_INIT); diff --git a/dts/bindings/firmware/arm,scmi-shmem.yaml b/dts/bindings/firmware/arm,scmi-shmem.yaml new file mode 100644 index 00000000000..aa968a3e4e5 --- /dev/null +++ b/dts/bindings/firmware/arm,scmi-shmem.yaml @@ -0,0 +1,12 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +description: System Control and Management Interface (SCMI) shared memory (SHMEM) + +compatible: "arm,scmi-shmem" + +include: [base.yaml] + +properties: + reg: + required: true diff --git a/dts/bindings/firmware/arm,scmi.yaml b/dts/bindings/firmware/arm,scmi.yaml new file mode 100644 index 00000000000..5ece8e59201 --- /dev/null +++ b/dts/bindings/firmware/arm,scmi.yaml @@ -0,0 +1,68 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +description: | + System Control and Management Interface (SCMI) with doorbell + and shared memory (SHMEM) transport. + + Devicetree example: + #include + + scmi_res0: memory@44611000 { + compatible = "arm,scmi-shmem"; + reg = <0x44611000 0x80>; + }; + + mu5: mailbox@44610000 { + compatible = "nxp,mbox-imx-mu"; + reg = <0x44610000 DT_SIZE_K(4)>; + interrupts = <205 0>; + #mbox-cells = <1>; + }; + + scmi { + compatible = "arm,scmi"; + shmem = <&scmi_res0>; + mboxes = <&mu5 0>; + mbox-names = "tx"; + + protocol@14 { + compatible = "arm,scmi-clock"; + reg = <0x14>; + #clock-cells = <1>; + }; + + protocol@19 { + compatible = "arm,scmi-pinctrl"; + reg = <0x19>; + + pinctrl { + compatible = "nxp,imx95-pinctrl", "nxp,imx93-pinctrl"; + }; + }; + }; + +compatible: "arm,scmi" + +include: [base.yaml] + +properties: + shmem: + type: phandle + required: true + description: | + Phandle to node describing TX channel shared memory area. + This translates to a **single** SCMI transmit channel. + + mboxes: + required: true + description: | + List of mailbox channel specifiers. It should contain one or two specifiers: + 1) tx - 1 mbox / 1 shmem (platform and agent use the same + mailbox channel for signaling) + 2) tx_reply - 2 mbox / 1 shmem (platform and agent use different + mailbox channels for signaling) + + mbox-names: + required: true + description: mailbox channel specifier names diff --git a/include/zephyr/drivers/firmware/scmi/protocol.h b/include/zephyr/drivers/firmware/scmi/protocol.h new file mode 100644 index 00000000000..13ac8ba248c --- /dev/null +++ b/include/zephyr/drivers/firmware/scmi/protocol.h @@ -0,0 +1,122 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief SCMI protocol generic functions and structures + */ + +#ifndef _INCLUDE_ZEPHYR_DRIVERS_FIRMWARE_SCMI_PROTOCOL_H_ +#define _INCLUDE_ZEPHYR_DRIVERS_FIRMWARE_SCMI_PROTOCOL_H_ + +#include +#include +#include +#include + +/** + * @brief Build an SCMI message header + * + * Builds an SCMI message header based on the + * fields that make it up. + * + * @param id message ID + * @param type message type + * @param proto protocol ID + * @param token message token + */ +#define SCMI_MESSAGE_HDR_MAKE(id, type, proto, token) \ + (SCMI_FIELD_MAKE(id, GENMASK(7, 0), 0) | \ + SCMI_FIELD_MAKE(type, GENMASK(1, 0), 8) | \ + SCMI_FIELD_MAKE(proto, GENMASK(7, 0), 10) | \ + SCMI_FIELD_MAKE(token, GENMASK(9, 0), 18)) + +struct scmi_channel; + +/** + * @brief SCMI message type + */ +enum scmi_message_type { + /** command message */ + SCMI_COMMAND = 0x0, + /** delayed reply message */ + SCMI_DELAYED_REPLY = 0x2, + /** notification message */ + SCMI_NOTIFICATION = 0x3, +}; + +/** + * @brief SCMI status codes + */ +enum scmi_status_code { + SCMI_SUCCESS = 0, + SCMI_NOT_SUPPORTED = -1, + SCMI_INVALID_PARAMETERS = -2, + SCMI_DENIED = -3, + SCMI_NOT_FOUND = -4, + SCMI_OUT_OF_RANGE = -5, + SCMI_BUSY = -6, + SCMI_COMMS_ERROR = -7, + SCMI_GENERIC_ERROR = -8, + SCMI_HARDWARE_ERROR = -9, + SCMI_PROTOCOL_ERROR = -10, + SCMI_IN_USE = -11, +}; + +/** + * @struct scmi_protocol + * + * @brief SCMI protocol structure + */ +struct scmi_protocol { + /** protocol ID */ + uint32_t id; + /** TX channel */ + struct scmi_channel *tx; + /** transport layer device */ + const struct device *transport; + /** protocol private data */ + void *data; +}; + +/** + * @struct scmi_message + * + * @brief SCMI message structure + */ +struct scmi_message { + uint32_t hdr; + uint32_t len; + void *content; +}; + +/** + * @brief Convert an SCMI status code to its Linux equivalent (if possible) + * + * @param scmi_status SCMI status code as shown in `enum scmi_status_code` + * + * @retval Linux equivalent status code + */ +int scmi_status_to_errno(int scmi_status); + +/** + * @brief Send an SCMI message and wait for its reply + * + * Blocking function used to send an SCMI message over + * a given channel and wait for its reply + * + * @param proto pointer to SCMI protocol + * @param msg pointer to SCMI message to send + * @param reply pointer to SCMI message in which the reply is to be + * written + * + * @retval 0 if successful + * @retval negative errno if failure + */ +int scmi_send_message(struct scmi_protocol *proto, + struct scmi_message *msg, struct scmi_message *reply); + +#endif /* _INCLUDE_ZEPHYR_DRIVERS_FIRMWARE_SCMI_PROTOCOL_H_ */ diff --git a/include/zephyr/drivers/firmware/scmi/shmem.h b/include/zephyr/drivers/firmware/scmi/shmem.h new file mode 100644 index 00000000000..a655a361e69 --- /dev/null +++ b/include/zephyr/drivers/firmware/scmi/shmem.h @@ -0,0 +1,67 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief SCMI SHMEM API + */ + +#ifndef _INCLUDE_ZEPHYR_DRIVERS_FIRMWARE_SCMI_SHMEM_H_ +#define _INCLUDE_ZEPHYR_DRIVERS_FIRMWARE_SCMI_SHMEM_H_ + +#include +#include +#include + +#define SCMI_SHMEM_CHAN_STATUS_BUSY_BIT BIT(0) +#define SCMI_SHMEM_CHAN_FLAG_IRQ_BIT BIT(0) + +struct scmi_message; + +/** + * @brief Write a message in the SHMEM area + * + * @param shmem pointer to shmem device + * @param msg message to write + * + * @retval 0 if successful + * @retval negative errno if failure + */ +int scmi_shmem_write_message(const struct device *shmem, + struct scmi_message *msg); + +/** + * @brief Read a message from a SHMEM area + * + * @param shmem pointer to shmem device + * @param msg message to write the data into + * + * @retval 0 if successful + * @retval negative errno if failure + */ +int scmi_shmem_read_message(const struct device *shmem, + struct scmi_message *msg); + +/** + * @brief Update the channel flags + * + * @param shmem pointer to shmem device + * @param mask value to negate and bitwise-and the old + * channel flags value + * @param val value to bitwise and with the mask and + * bitwise-or with the masked old value + */ +void scmi_shmem_update_flags(const struct device *shmem, + uint32_t mask, uint32_t val); + +/** + * @brief Read a channel's status + * + * @param shmem pointer to shmem device + */ +uint32_t scmi_shmem_channel_status(const struct device *shmem); + +#endif /* _INCLUDE_ZEPHYR_DRIVERS_FIRMWARE_SCMI_SHMEM_H_ */ diff --git a/include/zephyr/drivers/firmware/scmi/transport.h b/include/zephyr/drivers/firmware/scmi/transport.h new file mode 100644 index 00000000000..7f52b0df264 --- /dev/null +++ b/include/zephyr/drivers/firmware/scmi/transport.h @@ -0,0 +1,274 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Public APIs for the SCMI transport layer drivers + */ + +#ifndef _INCLUDE_ZEPHYR_DRIVERS_FIRMWARE_SCMI_TRANSPORT_H_ +#define _INCLUDE_ZEPHYR_DRIVERS_FIRMWARE_SCMI_TRANSPORT_H_ + +#include +#include + +struct scmi_message; +struct scmi_channel; + +/** + * @typedef scmi_channel_cb + * + * @brief Callback function for message replies + * + * This function should be called by the transport layer + * driver whenever a reply to a previously sent message + * has been received. Its purpose is to notifying the SCMI + * core of the reply's arrival so that proper action can + * be taken. + * + * @param chan pointer to SCMI channel on which the reply + * arrived + */ +typedef void (*scmi_channel_cb)(struct scmi_channel *chan); + +/** + * @struct scmi_channel + * @brief SCMI channel structure + * + * An SCMI channel is a medium through which a protocol + * is able to transmit/receive messages. Each of the SCMI + * channels is represented by a `struct scmi_channel`. + */ +struct scmi_channel { + /** + * channel lock. This is meant to be initialized + * and used only by the SCMI core to assure that + * only one protocol can send/receive messages + * through a channel at a given moment. + */ + struct k_mutex lock; + /** + * binary semaphore. This is meant to be initialized + * and used only by the SCMI core. Its purpose is to + * signal that a reply has been received. + */ + struct k_sem sem; + /** channel private data */ + void *data; + /** + * callback function. This is meant to be set by + * the SCMI core and should be called by the SCMI + * transport layer driver whenever a reply has + * been received. + */ + scmi_channel_cb cb; + /** is the channel ready to be used by a protocol? */ + bool ready; +}; + +struct scmi_transport_api { + int (*init)(const struct device *transport); + int (*send_message)(const struct device *transport, + struct scmi_channel *chan, + struct scmi_message *msg); + int (*setup_chan)(const struct device *transport, + struct scmi_channel *chan, + bool tx); + int (*read_message)(const struct device *transport, + struct scmi_channel *chan, + struct scmi_message *msg); + bool (*channel_is_free)(const struct device *transport, + struct scmi_channel *chan); + struct scmi_channel *(*request_channel)(const struct device *transport, + uint32_t proto, bool tx); +}; + +/** + * @brief Request an SCMI channel dynamically + * + * Whenever the SCMI transport layer driver doesn't support + * static channel allocation, the SCMI core will try to bind + * a channel to a protocol dynamically using this function. + * Note that no setup needs to be performed on the channel + * in this function as the core will also call the channel + * setup() function. + * + * @param transport pointer to the device structure for the + * transport layer + * @param proto ID of the protocol for which the core is + * requesting the channel + * @param tx true if the channel is TX, false if RX + * + * @retval pointer to SCMI channel that's to be bound + * to the protocol + * @retval NULL if operation was not successful + */ +static inline struct scmi_channel * +scmi_transport_request_channel(const struct device *transport, + uint32_t proto, bool tx) +{ + const struct scmi_transport_api *api = + (const struct scmi_transport_api *)transport->api; + + if (api->request_channel) { + return api->request_channel(transport, proto, tx); + } + + return NULL; +} + +/** + * @brief Perform initialization for the transport layer driver + * + * The transport layer driver can't be initialized directly + * (i.e via a call to its init() function) during system initialization. + * This is because the macro used to define an SCMI transport places + * `scmi_core_transport_init()` in the init section instead of the + * driver's init() function. As such, `scmi_core_transport_init()` + * needs to call this function to perfrom transport layer driver + * initialization if required. + * + * This operation is optional. + * + * @param transport pointer to the device structure for the + * transport layer + * + * @retval 0 if successful + * @retval negative errno code if failure + */ +static inline int scmi_transport_init(const struct device *transport) +{ + const struct scmi_transport_api *api = + (const struct scmi_transport_api *)transport->api; + + if (api->init) { + return api->init(transport); + } + + return 0; +} + +/** + * @brief Setup an SCMI channel + * + * Before being able to send/receive messages, an SCMI channel needs + * to be prepared, which is what this function does. If it returns + * successfully, an SCMI protocol will be able to use this channel + * to send/receive messages. + * + * @param transport pointer to the device structure for the + * transport layer + * @param chan pointer to SCMI channel to be prepared + * @param tx true if the channel is TX, false if RX + * + * @retval 0 if successful + * @retval negative errno code if failure + */ +static inline int scmi_transport_setup_chan(const struct device *transport, + struct scmi_channel *chan, + bool tx) +{ + const struct scmi_transport_api *api = + (const struct scmi_transport_api *)transport->api; + + if (!api || !api->setup_chan) { + return -ENOSYS; + } + + return api->setup_chan(transport, chan, tx); +} + +/** + * @brief Send an SCMI channel + * + * Send an SCMI message using given SCMI channel. This function is + * not allowed to block. + * + * @param transport pointer to the device structure for the + * transport layer + * @param chan pointer to SCMI channel on which the message + * is to be sent + * @param msg pointer to message the caller wishes to send + * + * @retval 0 if successful + * @retval negative errno code if failure + */ +static inline int scmi_transport_send_message(const struct device *transport, + struct scmi_channel *chan, + struct scmi_message *msg) +{ + const struct scmi_transport_api *api = + (const struct scmi_transport_api *)transport->api; + + if (!api || !api->send_message) { + return -ENOSYS; + } + + return api->send_message(transport, chan, msg); +} + +/** + * @brief Read an SCMI message + * + * @param transport pointer to the device structure for the + * transport layer + * @param chan pointer to SCMI channel on which the message + * is to be read + * @param msg pointer to message the caller wishes to read + * + * @retval 0 if successful + * @retval negative errno code if failure + */ +static inline int scmi_transport_read_message(const struct device *transport, + struct scmi_channel *chan, + struct scmi_message *msg) +{ + const struct scmi_transport_api *api = + (const struct scmi_transport_api *)transport->api; + + if (!api || !api->read_message) { + return -ENOSYS; + } + + return api->read_message(transport, chan, msg); +} + +/** + * @brief Check if an SCMI channel is free + * + * @param transport pointer to the device structure for + * the transport layer + * @param chan pointer to SCMI channel the query is to be + * performed on + * + * @retval 0 if successful + * @retval negative errno code if failure + */ +static inline bool scmi_transport_channel_is_free(const struct device *transport, + struct scmi_channel *chan) +{ + const struct scmi_transport_api *api = + (const struct scmi_transport_api *)transport->api; + + if (!api || !api->channel_is_free) { + return -ENOSYS; + } + + return api->channel_is_free(transport, chan); +} + +/** + * @brief Perfrom SCMI core initialization + * + * @param transport pointer to the device structure for + * the transport layer + * + * @retval 0 if successful + * @retval negative errno code if failure + */ +int scmi_core_transport_init(const struct device *transport); + +#endif /* _INCLUDE_ZEPHYR_DRIVERS_FIRMWARE_SCMI_TRANSPORT_H_ */ diff --git a/include/zephyr/drivers/firmware/scmi/util.h b/include/zephyr/drivers/firmware/scmi/util.h new file mode 100644 index 00000000000..ac22b10efc4 --- /dev/null +++ b/include/zephyr/drivers/firmware/scmi/util.h @@ -0,0 +1,280 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ARM SCMI utility header + * + * Contains various utility macros and macros used for protocol and + * transport "registration". + */ + +#ifndef _INCLUDE_ZEPHYR_DRIVERS_FIRMWARE_SCMI_UTIL_H_ +#define _INCLUDE_ZEPHYR_DRIVERS_FIRMWARE_SCMI_UTIL_H_ + +/** + * @brief Build protocol name from its ID + * + * Given a protocol ID, this macro builds the protocol + * name. This is done by concatenating the scmi_protocol_ + * construct with the given protocol ID. + * + * @param proto protocol ID in decimal format + * + * @return protocol name + */ +#define SCMI_PROTOCOL_NAME(proto) CONCAT(scmi_protocol_, proto) + +#ifdef CONFIG_ARM_SCMI_TRANSPORT_HAS_STATIC_CHANNELS + +#ifdef CONFIG_ARM_SCMI_MAILBOX_TRANSPORT +/** @brief Check if a protocol node has an associated channel + * + * This macro, when applied to a protocol node, checks if + * the node has a dedicated static channel allocated to it. + * This definition is specific to the mailbox driver and + * each new transport layer driver should define its own + * version of this macro based on the devicetree properties + * that indicate the presence of a dedicated channel. + * + * @param node_id protocol node identifier + * @idx channel index. Should be 0 for TX channels and 1 for + * RX channels + */ +#define DT_SCMI_TRANSPORT_PROTO_HAS_CHAN(node_id, idx)\ + DT_PROP_HAS_IDX(node_id, shmem, idx) +#else /* CONFIG_ARM_SCMI_MAILBOX_TRANSPORT */ +#error "Transport with static channels needs to define HAS_CHAN macro" +#endif /* CONFIG_ARM_SCMI_MAILBOX_TRANSPORT */ + +#define SCMI_TRANSPORT_CHAN_NAME(proto, idx) CONCAT(scmi_channel_, proto, _, idx) + +/** + * @brief Declare a TX SCMI channel + * + * Given a node_id for a protocol, this macro declares the SCMI + * TX channel statically bound to said protocol via the "extern" + * qualifier. This is useful when the transport layer driver + * supports static channels since all channel structures are + * defined inside the transport layer driver. + * + * @param node_id protocol node identifier + */ +#define DT_SCMI_TRANSPORT_TX_CHAN_DECLARE(node_id) \ + COND_CODE_1(DT_SCMI_TRANSPORT_PROTO_HAS_CHAN(node_id, 0), \ + (extern struct scmi_channel \ + SCMI_TRANSPORT_CHAN_NAME(DT_REG_ADDR(node_id), 0);), \ + (extern struct scmi_channel \ + SCMI_TRANSPORT_CHAN_NAME(SCMI_PROTOCOL_BASE, 0);)) \ + +/** + * @brief Declare SCMI TX/RX channels + * + * Given a node_id for a protocol, this macro declares the + * SCMI TX and RX channels statically bound to said protocol via + * the "extern" qualifier. Since RX channels are currently not + * supported, this is equivalent to DT_SCMI_TRANSPORT_TX_CHAN_DECLARE(). + * Despite this, users should opt for this macro instead of the TX-specific + * one. + * + * @param node_id protocol node identifier + */ +#define DT_SCMI_TRANSPORT_CHANNELS_DECLARE(node_id) \ + DT_SCMI_TRANSPORT_TX_CHAN_DECLARE(node_id) \ + +/** + * @brief Declare SCMI TX/RX channels using node instance number + * + * Same as DT_SCMI_TRANSPORT_CHANNELS_DECLARE() but uses the + * protocol's node instance number and the DT_DRV_COMPAT macro. + * + * @param protocol node instance number + */ +#define DT_INST_SCMI_TRANSPORT_CHANNELS_DECLARE(inst) \ + DT_SCMI_TRANSPORT_CHANNELS_DECLARE(DT_INST(inst, DT_DRV_COMPAT)) + +/** + * @brief Get a reference to a protocol's SCMI TX channel + * + * Given a node_id for a protocol, this macro returns a + * reference to an SCMI TX channel statically bound to said + * protocol. + * + * @param node_id protocol node identifier + * + * @return reference to the struct scmi_channel of the TX channel + * bound to the protocol identifier by node_id + */ +#define DT_SCMI_TRANSPORT_TX_CHAN(node_id) \ + COND_CODE_1(DT_SCMI_TRANSPORT_PROTO_HAS_CHAN(node_id, 0), \ + (&SCMI_TRANSPORT_CHAN_NAME(DT_REG_ADDR(node_id), 0)), \ + (&SCMI_TRANSPORT_CHAN_NAME(SCMI_PROTOCOL_BASE, 0))) + +/** + * @brief Define an SCMI channel for a protocol + * + * This macro defines a struct scmi_channel for a given protocol. + * This should be used by the transport layer driver to statically + * define SCMI channels for the protocols. + * + * @param node_id protocol node identifier + * @param idx channel index. Should be 0 for TX channels and 1 + * for RX channels + * @param proto protocol ID in decimal format + */ +#define DT_SCMI_TRANSPORT_CHAN_DEFINE(node_id, idx, proto, pdata) \ + struct scmi_channel SCMI_TRANSPORT_CHAN_NAME(proto, idx) = \ + { \ + .data = pdata, \ + } + +/** + * @brief Define an SCMI protocol's data + * + * Each SCMI protocol is identified by a struct scmi_protocol + * placed in a linker section called scmi_protocol. Each protocol + * driver is required to use this macro for "registration". Using + * this macro directly is higly discouraged and users should opt + * for macros such as DT_SCMI_PROTOCOL_DEFINE_NODEV() or + * DT_SCMI_PROTOCOL_DEFINE(), which also takes care of the static + * channel declaration (if applicable). + * + * @param node_id protocol node identifier + * @param proto protocol ID in decimal format + * @param pdata protocol private data + */ +#define DT_SCMI_PROTOCOL_DATA_DEFINE(node_id, proto, pdata) \ + STRUCT_SECTION_ITERABLE(scmi_protocol, SCMI_PROTOCOL_NAME(proto)) = \ + { \ + .id = proto, \ + .tx = DT_SCMI_TRANSPORT_TX_CHAN(node_id), \ + .data = pdata, \ + } + +#else /* CONFIG_ARM_SCMI_TRANSPORT_HAS_STATIC_CHANNELS */ + +#define DT_SCMI_TRANSPORT_CHANNELS_DECLARE(node_id) + +#define DT_SCMI_PROTOCOL_DATA_DEFINE(node_id, proto, pdata) \ + STRUCT_SECTION_ITERABLE(scmi_protocol, SCMI_PROTOCOL_NAME(proto)) = \ + { \ + .id = proto, \ + .data = pdata, \ + } + +#endif /* CONFIG_ARM_SCMI_TRANSPORT_HAS_STATIC_CHANNELS */ + +/** + * @brief Define an SCMI transport driver + * + * This is merely a wrapper over DEVICE_DT_INST_DEFINE(), but is + * required since transport layer drivers are not allowed to place + * their own init() function in the init section. Instead, transport + * layer drivers place the scmi_core_transport_init() function in the + * init section, which, in turn, will call the transport layer driver + * init() function. This is required because the SCMI core needs to + * perform channel binding and setup during the transport layer driver's + * initialization. + */ +#define DT_INST_SCMI_TRANSPORT_DEFINE(inst, pm, data, config, level, prio, api) \ + DEVICE_DT_INST_DEFINE(inst, &scmi_core_transport_init, \ + pm, data, config, level, prio, api) + +/** + * @brief Define an SCMI protocol + * + * This macro performs three important functions: + * 1) It defines a `struct scmi_protocol`, which is + * needed by all protocol drivers to work with the SCMI API. + * + * 2) It declares the static channels bound to the protocol. + * This is only applicable if the transport layer driver + * supports static channels. + * + * 3) It creates a `struct device` a sets the `data` field + * to the newly defined `struct scmi_protocol`. This is + * needed because the protocol driver needs to work with the + * SCMI API **and** the subsystem API. + * + * @param node_id protocol node identifier + * @param init_fn pointer to protocol's initialization function + * @param api pointer to protocol's subsystem API + * @param pm pointer to the protocol's power management resources + * @param data pointer to protocol's private data + * @param config pointer to protocol's private constant data + * @param level protocol initialization level + * @param prio protocol's priority within its initialization level + */ +#define DT_SCMI_PROTOCOL_DEFINE(node_id, init_fn, pm, data, config, \ + level, prio, api) \ + DT_SCMI_TRANSPORT_CHANNELS_DECLARE(node_id) \ + DT_SCMI_PROTOCOL_DATA_DEFINE(node_id, DT_REG_ADDR(node_id), data); \ + DEVICE_DT_DEFINE(node_id, init_fn, pm, \ + &SCMI_PROTOCOL_NAME(DT_REG_ADDR(node_id)), \ + config, level, prio, api) + +/** + * @brief Just like DT_SCMI_PROTOCOL_DEFINE(), but uses an instance + * of a `DT_DRV_COMPAT` compatible instead of a node identifier + * + * @param inst instance number + * @param ... other parameters as expected by DT_SCMI_PROTOCOL_DEFINE() + */ +#define DT_INST_SCMI_PROTOCOL_DEFINE(inst, init_fn, pm, data, config, \ + level, prio, api) \ + DT_SCMI_PROTOCOL_DEFINE(DT_INST(inst, DT_DRV_COMPAT), init_fn, pm, \ + data, config, level, prio, api) + +/** + * @brief Define an SCMI protocol with no device + * + * Variant of DT_SCMI_PROTOCOL_DEFINE(), but no `struct device` is + * created and no initialization function is called during system + * initialization. This is useful for protocols that are not really + * part of a subsystem with an API (e.g: pinctrl). + * + * @param node_id protocol node identifier + * @param data protocol private data + */ +#define DT_SCMI_PROTOCOL_DEFINE_NODEV(node_id, data) \ + DT_SCMI_TRANSPORT_CHANNELS_DECLARE(node_id) \ + DT_SCMI_PROTOCOL_DATA_DEFINE(node_id, DT_REG_ADDR(node_id), data) + +/** + * @brief Create an SCMI message field + * + * Data might not necessarily be encoded in the first + * x bits of an SCMI message parameter/return value. + * This comes in handy when building said parameters/ + * return values. + * + * @param x value to encode + * @param mask value to perform bitwise-and with `x` + * @param shift value to left-shift masked `x` + */ +#define SCMI_FIELD_MAKE(x, mask, shift)\ + (((uint32_t)(x) & (mask)) << (shift)) + +/** + * @brief SCMI protocol IDs + * + * Each SCMI protocol is identified by an ID. Each + * of these IDs needs to be in decimal since they + * might be used to build protocol and static channel + * names. + */ +#define SCMI_PROTOCOL_BASE 16 +#define SCMI_PROTOCOL_POWER_DOMAIN 17 +#define SCMI_PROTOCOL_SYSTEM 18 +#define SCMI_PROTOCOL_PERF 19 +#define SCMI_PROTOCOL_CLOCK 20 +#define SCMI_PROTOCOL_SENSOR 21 +#define SCMI_PROTOCOL_RESET_DOMAIN 22 +#define SCMI_PROTOCOL_VOLTAGE_DOMAIN 23 +#define SCMI_PROTOCOL_PCAP_MONITOR 24 +#define SCMI_PROTOCOL_PINCTRL 25 + +#endif /* _INCLUDE_ZEPHYR_DRIVERS_FIRMWARE_SCMI_UTIL_H_ */ diff --git a/include/zephyr/linker/common-ram.ld b/include/zephyr/linker/common-ram.ld index ecca31165a6..1eef183d4ae 100644 --- a/include/zephyr/linker/common-ram.ld +++ b/include/zephyr/linker/common-ram.ld @@ -12,6 +12,10 @@ #endif #endif /* NETWORKING */ +#ifdef CONFIG_ARM_SCMI +ITERABLE_SECTION_RAM(scmi_protocol, Z_LINK_ITERABLE_SUBALIGN) +#endif /* CONFIG_ARM_SCMI */ + #if defined(CONFIG_GEN_SW_ISR_TABLE) && defined(CONFIG_DYNAMIC_INTERRUPTS) SECTION_DATA_PROLOGUE(sw_isr_table,,) {