diff --git a/MAINTAINERS.yml b/MAINTAINERS.yml index ada6185662d..8e95117a252 100644 --- a/MAINTAINERS.yml +++ b/MAINTAINERS.yml @@ -2008,6 +2008,23 @@ Release Notes: tests: - sample.drivers.espi.ps2 +"Drivers: PSI5": + status: maintained + maintainers: + - manuargue + - Dat-NguyenDuy + collaborators: + - congnguyenhuu + files: + - drivers/psi5/ + - include/zephyr/drivers/psi5/ + - dts/bindings/psi5/ + - doc/hardware/peripherals/psi5.rst + labels: + - "area: PSI5" + tests: + - drivers.psi5 + "Drivers: PTP Clock": status: maintained maintainers: diff --git a/doc/hardware/peripherals/index.rst b/doc/hardware/peripherals/index.rst index 9a4d7e9edfe..1b235d242c3 100644 --- a/doc/hardware/peripherals/index.rst +++ b/doc/hardware/peripherals/index.rst @@ -47,6 +47,7 @@ Peripherals pcie.rst peci.rst ps2.rst + psi5.rst pwm.rst rtc.rst regulators.rst diff --git a/doc/hardware/peripherals/psi5.rst b/doc/hardware/peripherals/psi5.rst new file mode 100644 index 00000000000..adcafdf27ba --- /dev/null +++ b/doc/hardware/peripherals/psi5.rst @@ -0,0 +1,22 @@ +.. _psi5_api: + +Peripheral Sensor Interface (PSI5) +################################## + +Overview +******** + +The PSI5 API provides functionality to communicate with Peripheral Sensor Interface (PSI5) +devices. + +Configuration Options +********************* + +Related configuration options: + +* :kconfig:option:`CONFIG_PSI5` + +API Reference +************* + +.. doxygengroup:: psi5_interface diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index 6838f746426..ad12a913160 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -74,6 +74,7 @@ add_subdirectory_ifdef(CONFIG_PINCTRL pinctrl) add_subdirectory_ifdef(CONFIG_PM_CPU_OPS pm_cpu_ops) add_subdirectory_ifdef(CONFIG_POWER_DOMAIN power_domain) add_subdirectory_ifdef(CONFIG_PS2 ps2) +add_subdirectory_ifdef(CONFIG_PSI5 psi5) add_subdirectory_ifdef(CONFIG_PTP_CLOCK ptp_clock) add_subdirectory_ifdef(CONFIG_PWM pwm) add_subdirectory_ifdef(CONFIG_REGULATOR regulator) diff --git a/drivers/Kconfig b/drivers/Kconfig index 2e71fecfb5f..d2ebb120032 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -71,6 +71,7 @@ source "drivers/pinctrl/Kconfig" source "drivers/pm_cpu_ops/Kconfig" source "drivers/power_domain/Kconfig" source "drivers/ps2/Kconfig" +source "drivers/psi5/Kconfig" source "drivers/ptp_clock/Kconfig" source "drivers/pwm/Kconfig" source "drivers/regulator/Kconfig" diff --git a/drivers/psi5/CMakeLists.txt b/drivers/psi5/CMakeLists.txt new file mode 100644 index 00000000000..2ffb1280512 --- /dev/null +++ b/drivers/psi5/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright 2025 NXP +# SPDX-License-Identifier: Apache-2.0 + +zephyr_syscall_header(${ZEPHYR_BASE}/include/zephyr/drivers/psi5/psi5.h) + +zephyr_library() + +zephyr_library_sources_ifdef(CONFIG_PSI5_NXP_S32 psi5_nxp_s32.c) diff --git a/drivers/psi5/Kconfig b/drivers/psi5/Kconfig new file mode 100644 index 00000000000..03b2a3d4d6a --- /dev/null +++ b/drivers/psi5/Kconfig @@ -0,0 +1,23 @@ +# Copyright 2025 NXP +# SPDX-License-Identifier: Apache-2.0 + +menuconfig PSI5 + bool "Peripheral Sensor Interface (PSI5) driver" + help + Enable PSI5 Driver Configuration + +if PSI5 + +module = PSI5 +module-str = psi5 +source "subsys/logging/Kconfig.template.log_config" + +config PSI5_INIT_PRIORITY + int "PSI5 driver init priority" + default KERNEL_INIT_PRIORITY_DEVICE + help + PSI5 driver device initialization priority. + +source "drivers/psi5/Kconfig.nxp_s32" + +endif # PSI5 diff --git a/drivers/psi5/Kconfig.nxp_s32 b/drivers/psi5/Kconfig.nxp_s32 new file mode 100644 index 00000000000..0711b6f5054 --- /dev/null +++ b/drivers/psi5/Kconfig.nxp_s32 @@ -0,0 +1,10 @@ +# Copyright 2025 NXP +# SPDX-License-Identifier: Apache-2.0 + +config PSI5_NXP_S32 + bool "NXP S32 PSI5 driver" + default y + depends on DT_HAS_NXP_S32_PSI5_ENABLED + select PINCTRL if $(dt_compat_any_has_prop,$(DT_COMPAT_NXP_S32_PSI5),pinctrl-0) + help + Enable support for NXP S32 PSI5 driver. diff --git a/drivers/psi5/psi5_nxp_s32.c b/drivers/psi5/psi5_nxp_s32.c new file mode 100644 index 00000000000..5556af50179 --- /dev/null +++ b/drivers/psi5/psi5_nxp_s32.c @@ -0,0 +1,521 @@ +/* + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT nxp_s32_psi5 + +#include + +LOG_MODULE_REGISTER(nxp_s32_psi5, CONFIG_PSI5_LOG_LEVEL); + +#include +#include +#include + +#include + +#include "Psi5_Ip.h" + +struct psi5_nxp_s32_config { + uint8_t ctrl_inst; + PSI5_Type *base; + uint8_t channel_mask; + const struct pinctrl_dev_config *pin_cfg; + void (*irq_config_func)(void); +}; + +struct psi5_nxp_s32_tx_callback { + psi5_tx_callback_t callback; + void *user_data; +}; + +struct psi5_nxp_s32_channel_data { + bool started; + bool async_mode; + struct psi5_nxp_s32_tx_callback tx_callback; + struct psi5_rx_callback_configs callback_configs; + uint32_t serial_frame_cnt; + uint32_t data_frame_cnt; + struct k_sem tx_sem; + struct k_mutex lock; +}; + +struct psi5_nxp_s32_data { + struct psi5_nxp_s32_channel_data channel_data[PSI5_CHANNEL_COUNT]; +}; + +struct psi5_nxp_s32_tx_default_cb_ctx { + struct k_sem done; + int status; +}; + +static int psi5_nxp_s32_start_sync(const struct device *dev, uint8_t channel) +{ + const struct psi5_nxp_s32_config *config = dev->config; + struct psi5_nxp_s32_data *data = dev->data; + struct psi5_nxp_s32_channel_data *channel_data = &data->channel_data[channel]; + int err; + + if (!(config->channel_mask & BIT(channel))) { + return -EINVAL; + } + + if (channel_data->started) { + return -EALREADY; + } + + if (channel_data->async_mode) { + return -ENOTSUP; + } + + k_mutex_lock(&channel_data->lock, K_FOREVER); + + err = Psi5_Ip_SetChannelSync(config->ctrl_inst, channel, true); + + if (err) { + LOG_ERR("Failed to start sync PSI5 %d channel %d", config->ctrl_inst, channel); + k_mutex_unlock(&channel_data->lock); + return -EIO; + } + + channel_data->started = true; + + k_mutex_unlock(&channel_data->lock); + + + return 0; +} + +static int psi5_nxp_s32_stop_sync(const struct device *dev, uint8_t channel) +{ + const struct psi5_nxp_s32_config *config = dev->config; + struct psi5_nxp_s32_data *data = dev->data; + struct psi5_nxp_s32_channel_data *channel_data = &data->channel_data[channel]; + int err; + + if (!(config->channel_mask & BIT(channel))) { + return -EINVAL; + } + + if (!channel_data->started) { + return -EALREADY; + } + + if (channel_data->async_mode) { + return -ENOTSUP; + } + + k_mutex_lock(&channel_data->lock, K_FOREVER); + + err = Psi5_Ip_SetChannelSync(config->ctrl_inst, channel, false); + + if (err) { + LOG_ERR("Failed to stop sync PSI5 %d channel %d", config->ctrl_inst, channel); + k_mutex_unlock(&channel_data->lock); + return -EIO; + } + + channel_data->started = false; + + k_mutex_unlock(&channel_data->lock); + + return 0; +} + +static int psi5_nxp_s32_do_send(const struct device *dev, uint8_t channel, uint64_t psi5_data, + k_timeout_t timeout, psi5_tx_callback_t callback, void *user_data) +{ + const struct psi5_nxp_s32_config *config = dev->config; + struct psi5_nxp_s32_data *data = dev->data; + struct psi5_nxp_s32_channel_data *channel_data = &data->channel_data[channel]; + int err; + + if (k_sem_take(&channel_data->tx_sem, timeout) != 0) { + return -EAGAIN; + } + + channel_data->tx_callback.callback = callback; + channel_data->tx_callback.user_data = user_data; + + err = Psi5_Ip_Transmit(config->ctrl_inst, channel, psi5_data); + if (err) { + LOG_ERR("Failed to transmit PSI5 %d channel %d", config->ctrl_inst, channel); + k_sem_give(&channel_data->tx_sem); + return -EIO; + } + + return 0; +} + +static void psi5_nxp_s32_tx_default_cb(const struct device *dev, uint8_t channel_id, + int status, void *user_data) +{ + ARG_UNUSED(dev); + ARG_UNUSED(channel_id); + struct psi5_nxp_s32_tx_default_cb_ctx *ctx = user_data; + + ctx->status = status; + + k_sem_give(&ctx->done); +} + +static int psi5_nxp_s32_send(const struct device *dev, uint8_t channel, uint64_t psi5_data, + k_timeout_t timeout, psi5_tx_callback_t callback, void *user_data) +{ + const struct psi5_nxp_s32_config *config = dev->config; + struct psi5_nxp_s32_data *data = dev->data; + struct psi5_nxp_s32_channel_data *channel_data = &data->channel_data[channel]; + + if (!(config->channel_mask & BIT(channel))) { + return -EINVAL; + } + + if (!channel_data->started) { + return -ENETDOWN; + } + + if (channel_data->async_mode) { + return -ENOTSUP; + } + + if (callback == NULL) { + struct psi5_nxp_s32_tx_default_cb_ctx ctx; + int err; + + k_sem_init(&ctx.done, 0, 1); + + err = psi5_nxp_s32_do_send(dev, channel, psi5_data, timeout, + psi5_nxp_s32_tx_default_cb, &ctx); + if (err != 0) { + return err; + } + + k_sem_take(&ctx.done, K_FOREVER); + + return ctx.status; + } + + return psi5_nxp_s32_do_send(dev, channel, psi5_data, timeout, callback, user_data); +} + +static int psi5_nxp_s32_register_callback(const struct device *dev, uint8_t channel, + struct psi5_rx_callback_configs callback_configs) +{ + const struct psi5_nxp_s32_config *config = dev->config; + struct psi5_nxp_s32_data *data = dev->data; + struct psi5_nxp_s32_channel_data *channel_data = &data->channel_data[channel]; + + if (!(config->channel_mask & BIT(channel))) { + return -EINVAL; + } + + k_mutex_lock(&channel_data->lock, K_FOREVER); + + channel_data->callback_configs = callback_configs; + + k_mutex_unlock(&channel_data->lock); + + return 0; +} + +static DEVICE_API(psi5, psi5_nxp_s32_driver_api) = { + .start_sync = psi5_nxp_s32_start_sync, + .stop_sync = psi5_nxp_s32_stop_sync, + .send = psi5_nxp_s32_send, + .register_callback = psi5_nxp_s32_register_callback, +}; + +#define PSI5_NXP_S32_HW_INSTANCE_CHECK(i, n) ((DT_INST_REG_ADDR(n) == IP_PSI5_##i##_BASE) ? i : 0) + +#define PSI5_NXP_S32_HW_INSTANCE(n) \ + LISTIFY(PSI5_INSTANCE_COUNT, PSI5_NXP_S32_HW_INSTANCE_CHECK, (|), n) + +#define PSI5_NXP_S32_CHANNEL_CALLBACK(node_id) \ + void _CONCAT(psi5_nxp_s32_channel_callBack, node_id)(Psi5_EventType event) \ + { \ + const struct device *dev = DEVICE_DT_GET(DT_PARENT(node_id)); \ + const struct psi5_nxp_s32_config *config = dev->config; \ + struct psi5_nxp_s32_data *data = dev->data; \ + uint8_t channel = DT_REG_ADDR(node_id); \ + struct psi5_nxp_s32_channel_data *channel_data = &data->channel_data[channel]; \ + struct psi5_nxp_s32_tx_callback *tx_callback = &channel_data->tx_callback; \ + struct psi5_rx_callback_config *serial_frame_cb = \ + channel_data->callback_configs.serial_frame; \ + struct psi5_rx_callback_config *data_frame_cb = \ + channel_data->callback_configs.data_frame; \ + \ + if (event.Psi5_DriverReadyToTransmit) { \ + if (tx_callback->callback) { \ + tx_callback->callback(dev, channel, 0, \ + tx_callback->user_data); \ + k_sem_give(&channel_data->tx_sem); \ + } \ + } else if (event.Psi5_TxDataOverwrite) { \ + if (tx_callback->callback) { \ + tx_callback->callback(dev, channel, -EIO, \ + tx_callback->user_data); \ + k_sem_give(&channel_data->tx_sem); \ + } \ + } else if (event.Psi5_Psi5MessageReceived && data_frame_cb) { \ + Psi5_Ip_Psi5FrameType ip_frame; \ + uint32_t *data_frame_cnt = &channel_data->data_frame_cnt; \ + Psi5_Ip_GetPsi5Frame(config->ctrl_inst, channel, &ip_frame); \ + \ + if (!!(ip_frame.C | ip_frame.F | ip_frame.EM | ip_frame.E | ip_frame.T)) { \ + data_frame_cb->callback(dev, channel, \ + *data_frame_cnt, \ + data_frame_cb->user_data); \ + *data_frame_cnt = 0; \ + } else { \ + data_frame_cb->frame[*data_frame_cnt].type = PSI5_DATA_FRAME; \ + data_frame_cb->frame[*data_frame_cnt].data = ip_frame.DATA_REGION; \ + data_frame_cb->frame[*data_frame_cnt].timestamp = \ + ip_frame.TIME_STAMP; \ + data_frame_cb->frame[*data_frame_cnt].crc = ip_frame.CRC; \ + data_frame_cb->frame[*data_frame_cnt].slot_number = \ + ip_frame.SLOT_COUNTER; \ + (*data_frame_cnt)++; \ + if (*data_frame_cnt == data_frame_cb->max_num_frame) { \ + data_frame_cb->callback(dev, channel, \ + *data_frame_cnt, \ + data_frame_cb->user_data); \ + *data_frame_cnt = 0; \ + } \ + } \ + } else if (event.Psi5_SmcMessageReceived && serial_frame_cb) { \ + Psi5_Ip_SmcFrameType ip_smc_frame; \ + uint32_t *serial_frame_cnt = &channel_data->serial_frame_cnt; \ + Psi5_Ip_GetSmcFrame(config->ctrl_inst, channel, &ip_smc_frame); \ + \ + if (!!(ip_smc_frame.CER | ip_smc_frame.OW)) { \ + serial_frame_cb->callback(dev, channel, \ + *serial_frame_cnt, \ + serial_frame_cb->user_data); \ + *serial_frame_cnt = 0; \ + } else { \ + if (ip_smc_frame.C) { \ + serial_frame_cb->frame[*serial_frame_cnt].type = \ + PSI5_SERIAL_FRAME_4_BIT_ID; \ + serial_frame_cb->frame[*serial_frame_cnt].serial.id = \ + ip_smc_frame.ID; \ + serial_frame_cb->frame[*serial_frame_cnt].serial.data = \ + FIELD_PREP(GENMASK(15, 12), \ + (ip_smc_frame.IDDATA)) | \ + FIELD_PREP(GENMASK(11, 0), (ip_smc_frame.DATA)); \ + } else { \ + serial_frame_cb->frame[*serial_frame_cnt].type = \ + PSI5_SERIAL_FRAME_8_BIT_ID; \ + serial_frame_cb->frame[0].serial.id = \ + FIELD_PREP(GENMASK(7, 4), (ip_smc_frame.ID)) | \ + FIELD_PREP(GENMASK(3, 0), (ip_smc_frame.IDDATA)); \ + serial_frame_cb->frame[0].serial.data = ip_smc_frame.DATA; \ + } \ + serial_frame_cb->frame[*serial_frame_cnt].crc = ip_smc_frame.CRC; \ + serial_frame_cb->frame[*serial_frame_cnt].slot_number = \ + ip_smc_frame.SLOT_NO; \ + (*serial_frame_cnt)++; \ + \ + if (*serial_frame_cnt == serial_frame_cb->max_num_frame) { \ + serial_frame_cb->callback( \ + dev, channel, \ + *serial_frame_cnt, serial_frame_cb->user_data); \ + *serial_frame_cnt = 0; \ + } \ + } \ + } \ + } + +#define PSI5_NXP_S32_SLOT_NODE(ch_node_id, slot) DT_CHILD(ch_node_id, slot_##slot) + +#define PSI5_NXP_S32_SLOT_CNT(slot, ch_node_id) \ + (DT_NODE_HAS_STATUS(PSI5_NXP_S32_SLOT_NODE(ch_node_id, slot), okay) ? 1 : 0) + +#define __PSI5_NXP_S32_CHANNEL_RX_SLOT_CONFIG(slot, ch_node_id) \ + { \ + .slotId = DT_REG_ADDR(PSI5_NXP_S32_SLOT_NODE(ch_node_id, slot)) + 1, \ + .slotLen = DT_PROP(PSI5_NXP_S32_SLOT_NODE(ch_node_id, slot), duration_us), \ + .startOffs = DT_PROP(PSI5_NXP_S32_SLOT_NODE(ch_node_id, slot), start_offset_us), \ + .dataSize = DT_PROP(PSI5_NXP_S32_SLOT_NODE(ch_node_id, slot), data_length), \ + .msbFirst = DT_PROP(PSI5_NXP_S32_SLOT_NODE(ch_node_id, slot), data_msb_first), \ + .hasSMC = DT_PROP(PSI5_NXP_S32_SLOT_NODE(ch_node_id, slot), has_smc), \ + .hasParity = DT_PROP(PSI5_NXP_S32_SLOT_NODE(ch_node_id, slot), has_parity), \ + }, + +#define _PSI5_NXP_S32_CHANNEL_RX_SLOT_CONFIG(slot, ch_node_id) \ + IF_ENABLED(DT_NODE_HAS_STATUS(PSI5_NXP_S32_SLOT_NODE(ch_node_id, slot), okay), \ + (__PSI5_NXP_S32_CHANNEL_RX_SLOT_CONFIG(slot, ch_node_id))) + +#define PSI5_NXP_S32_CHANNEL_RX_SLOT_CONFIG(node_id) \ + static const Psi5_Ip_SlotConfigType _CONCAT(psi5_nxp_s32_channel_rx_slot_config, \ + node_id)[PSI5_CHANNEL_CH_SFCR_COUNT] = { \ + LISTIFY(PSI5_CHANNEL_CH_SFCR_COUNT, \ + _PSI5_NXP_S32_CHANNEL_RX_SLOT_CONFIG, (), node_id)}; + +#define PSI5_NXP_S32_CHANNEL_RX_CONFIG(node_id) \ + const Psi5_Ip_ChannelRxConfigType _CONCAT(psi5_nxp_s32_channel_rx_config, node_id) = { \ + .rxBufSize = DT_PROP(node_id, num_rx_buf), \ + .bitRate = DT_ENUM_IDX(node_id, rx_bitrate_kbps), \ + .slotConfig = &_CONCAT(psi5_nxp_s32_channel_rx_slot_config, node_id)[0], \ + .numOfSlotConfigs = \ + LISTIFY(PSI5_CHANNEL_CH_SFCR_COUNT, PSI5_NXP_S32_SLOT_CNT, (+), node_id), \ + .watermarkInterruptLevel = GENMASK(UTIL_DEC(DT_PROP(node_id, num_rx_buf)), 0), \ + }; + +#define PSI5_NXP_S32_CHANNEL_TX_CONFIG(node_id) \ + const Psi5_Ip_ChannelTxConfigType _CONCAT(psi5_nxp_s32_channel_tx_config, node_id) = { \ + .targetPulse = DT_PROP_OR(node_id, period_sync_pulse_us, 0), \ + .decoderOffset = DT_PROP_OR(node_id, decoder_start_offset_us, 0), \ + .pulse0Width = DT_PROP_OR(node_id, pulse_width_0_us, 0), \ + .pulse1Width = DT_PROP_OR(node_id, pulse_width_1_us, 0), \ + .txMode = DT_ENUM_IDX_OR(node_id, tx_frame, 0), \ + .syncState = PSI5_SYNC_STATE_2, \ + .txSize = 64, /* This setting is applicable only in NON STANDARD FRAME */ \ + }; + +#define PSI5_NXP_S32_CHANNEL_ERR_SEL_CONFIG(node_id) \ + const Psi5_Ip_ErrorSelectConfigType _CONCAT(psi5_nxp_s32_channel_err_sel_config, \ + node_id) = { \ + .errorSelect0 = true, \ + .errorSelect1 = true, \ + .errorSelect2 = true, \ + .errorSelect3 = true, \ + .errorSelect4 = true, \ + }; + +/* + * The macro get index of array configuration that corresponds to each the ID of HW channel. + * Assign 0xff to unused channels. + */ + +#define PSI5_NXP_S32_CHANNEL_NODE(n, i) DT_INST_CHILD(n, DT_CAT(ch_, i)) + +#define PSI5_NXP_S32_ID_CFG_CNT(i, node_id, n) \ + (DT_NODE_HAS_STATUS(PSI5_NXP_S32_CHANNEL_NODE(n, i), okay) && \ + (DT_REG_ADDR(PSI5_NXP_S32_CHANNEL_NODE(n, i)) < (DT_REG_ADDR(node_id))) \ + ? 1 \ + : 0) + +#define PSI5_NXP_S32_ID_CFG(node_id, n) \ + COND_CODE_1(DT_NODE_HAS_STATUS(node_id, okay), \ + (LISTIFY(PSI5_CHANNEL_COUNT, PSI5_NXP_S32_ID_CFG_CNT, (+), node_id, n),), (0xff,)) + +#define PSI5_NXP_S32_CHANNEL_CONFIG(node_id) \ + { \ + .channelId = DT_REG_ADDR(node_id), \ + .channelMode = !DT_PROP(node_id, async_mode), \ + .callback = _CONCAT(psi5_nxp_s32_channel_callBack, node_id), \ + .rxConfig = &_CONCAT(psi5_nxp_s32_channel_rx_config, node_id), \ + .txConfig = &_CONCAT(psi5_nxp_s32_channel_tx_config, node_id), \ + .errorSelectConfig = &_CONCAT(psi5_nxp_s32_channel_err_sel_config, node_id), \ + }, + +/* Define array channel configuration */ +#define PSI5_NXP_S32_ARRAY_CHANNEL_CONFIG(n) \ + DT_INST_FOREACH_CHILD_STATUS_OKAY(n, PSI5_NXP_S32_CHANNEL_CALLBACK) \ + DT_INST_FOREACH_CHILD_STATUS_OKAY(n, PSI5_NXP_S32_CHANNEL_RX_SLOT_CONFIG) \ + DT_INST_FOREACH_CHILD_STATUS_OKAY(n, PSI5_NXP_S32_CHANNEL_RX_CONFIG) \ + DT_INST_FOREACH_CHILD_STATUS_OKAY(n, PSI5_NXP_S32_CHANNEL_TX_CONFIG) \ + DT_INST_FOREACH_CHILD_STATUS_OKAY(n, PSI5_NXP_S32_CHANNEL_ERR_SEL_CONFIG) \ + const Psi5_Ip_ChannelConfigType \ + psi5_nxp_s32_channel_array_config_##n[DT_INST_CHILD_NUM_STATUS_OKAY(n)] = { \ + DT_INST_FOREACH_CHILD_STATUS_OKAY(n, PSI5_NXP_S32_CHANNEL_CONFIG)}; \ + const uint8_t psi5_nxp_s32_map_idex_array_config_##n[PSI5_CHANNEL_COUNT] = { \ + DT_INST_FOREACH_CHILD_VARGS(n, PSI5_NXP_S32_ID_CFG, n)}; + +DT_INST_FOREACH_STATUS_OKAY(PSI5_NXP_S32_ARRAY_CHANNEL_CONFIG) + +/* Define array instance configuration */ +#define PSI5_NXP_S32_INST_CONFIG(n) \ + { \ + .instanceId = PSI5_NXP_S32_HW_INSTANCE(n), \ + .channelConfig = &psi5_nxp_s32_channel_array_config_##n[0], \ + .numOfChannels = DT_INST_CHILD_NUM_STATUS_OKAY(n), \ + .chHwIdToIndexArrayConfig = &psi5_nxp_s32_map_idex_array_config_##n[0], \ + }, + +static const Psi5_Ip_InstanceType psi5_nxp_s32_array_inst_config[DT_NUM_INST_STATUS_OKAY( + DT_DRV_COMPAT)] = {DT_INST_FOREACH_STATUS_OKAY(PSI5_NXP_S32_INST_CONFIG)}; + +/* The structure configuration for all controller instances that used for Psi5_Ip_Init() */ +static const Psi5_Ip_ConfigType psi5_nxp_s32_controller_config = { + .instancesConfig = &psi5_nxp_s32_array_inst_config[0], + .numOfInstances = DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT), +}; + +#define PSI5_NXP_S32_CHANNEL_ISR(node_id) \ + static void _CONCAT(psi5_nxp_s32_channel_isr, node_id)(const struct device *dev) \ + { \ + const struct psi5_nxp_s32_config *config = dev->config; \ + \ + Psi5_Ip_IRQ_Handler(config->ctrl_inst, DT_REG_ADDR(node_id)); \ + } + +#define PSI5_NXP_S32_CHANNEL_IRQ_CONFIG(node_id, n) \ + do { \ + IRQ_CONNECT(DT_IRQ_BY_IDX(node_id, 0, irq), DT_IRQ_BY_IDX(node_id, 0, priority), \ + _CONCAT(psi5_nxp_s32_channel_isr, node_id), DEVICE_DT_INST_GET(n), \ + DT_IRQ_BY_IDX(node_id, 0, flags)); \ + irq_enable(DT_IRQN(node_id)); \ + } while (false); + +#define PSI5_NXP_S32_IRQ_CONFIG(n) \ + DT_INST_FOREACH_CHILD_STATUS_OKAY(n, PSI5_NXP_S32_CHANNEL_ISR) \ + static void psi5_irq_config_##n(void) \ + { \ + DT_INST_FOREACH_CHILD_STATUS_OKAY_VARGS(n, PSI5_NXP_S32_CHANNEL_IRQ_CONFIG, n) \ + } + +#define PSI5_NXP_S32_CHANNEL_BIT_MASK(node_id) BIT(DT_REG_ADDR(node_id)) + +#define PSI5_NXP_S32_CHANNEL_ASYNC_MODE(node_id) \ + data->channel_data[DT_REG_ADDR(node_id)].async_mode = DT_PROP(node_id, async_mode); + +#define DEV_PSI5_NXP_S32_INIT(n) \ + PINCTRL_DT_INST_DEFINE(n); \ + PSI5_NXP_S32_IRQ_CONFIG(n) \ + static struct psi5_nxp_s32_config psi5_nxp_s32_config_##n = { \ + .ctrl_inst = PSI5_NXP_S32_HW_INSTANCE(n), \ + .base = (PSI5_Type *)DT_INST_REG_ADDR(n), \ + .channel_mask = DT_INST_FOREACH_CHILD_STATUS_OKAY_SEP( \ + n, PSI5_NXP_S32_CHANNEL_BIT_MASK, (|)), \ + .pin_cfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ + .irq_config_func = psi5_irq_config_##n, \ + }; \ + static struct psi5_nxp_s32_data psi5_nxp_s32_data_##n; \ + static int psi5_nxp_s32_init_##n(const struct device *dev) \ + { \ + const struct psi5_nxp_s32_config *config = dev->config; \ + struct psi5_nxp_s32_data *data = dev->data; \ + int err = 0; \ + \ + err = pinctrl_apply_state(config->pin_cfg, PINCTRL_STATE_DEFAULT); \ + if (err < 0) { \ + LOG_ERR("PSI5 pinctrl setup failed (%d)", err); \ + return err; \ + } \ + \ + for (int ch = 0; ch < PSI5_CHANNEL_COUNT; ch++) { \ + if (config->channel_mask & BIT(ch)) { \ + k_sem_init(&data->channel_data[ch].tx_sem, 1, 1); \ + k_mutex_init(&data->channel_data[ch].lock); \ + } \ + } \ + \ + DT_INST_FOREACH_CHILD_STATUS_OKAY(n, PSI5_NXP_S32_CHANNEL_ASYNC_MODE) \ + \ + /* Common configuration setup for all controller instances */ \ + if (n == UTIL_DEC(DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT))) { \ + Psi5_Ip_Init(&psi5_nxp_s32_controller_config); \ + } \ + \ + config->irq_config_func(); \ + \ + return 0; \ + } \ + DEVICE_DT_INST_DEFINE(n, psi5_nxp_s32_init_##n, NULL, &psi5_nxp_s32_data_##n, \ + &psi5_nxp_s32_config_##n, POST_KERNEL, CONFIG_PSI5_INIT_PRIORITY, \ + &psi5_nxp_s32_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(DEV_PSI5_NXP_S32_INIT) diff --git a/dts/bindings/psi5/nxp,s32-psi5.yaml b/dts/bindings/psi5/nxp,s32-psi5.yaml new file mode 100644 index 00000000000..bd7f341fc13 --- /dev/null +++ b/dts/bindings/psi5/nxp,s32-psi5.yaml @@ -0,0 +1,32 @@ +# Copyright 2025 NXP +# +# SPDX-License-Identifier: Apache-2.0 + +description: NXP S32 PSI5 (Peripheral Sensor Interface) Controller + +compatible: "nxp,s32-psi5" + +include: [psi5-controller.yaml, pinctrl-device.yaml] + +properties: + pinctrl-0: + required: true + + pinctrl-names: + required: true + +child-binding: + + properties: + interrupts: + type: array + required: true + description: Information about the channel interrupts. + + num-rx-buf: + type: int + required: true + description: | + Specifies the maximum number of receive buffers used for storing PSI5 messages. + The value can range from 1 to 32, determining how many messages can be stored in + the buffer at any given time. diff --git a/dts/bindings/psi5/psi5-controller.yaml b/dts/bindings/psi5/psi5-controller.yaml new file mode 100644 index 00000000000..2e7f07708d2 --- /dev/null +++ b/dts/bindings/psi5/psi5-controller.yaml @@ -0,0 +1,141 @@ +# Copyright 2025 NXP +# +# SPDX-License-Identifier: Apache-2.0 + +description: PSI5 (Peripheral Sensor Interface) Controller + +include: base.yaml + +properties: + reg: + required: true + +child-binding: + description: | + Each child node defines the configuration of a PSI5 channel + + properties: + reg: + type: int + required: true + description: Channel identifier. + + async-mode: + type: boolean + description: | + Determines the channel operation mode. When set to true, the channel operates in + asynchronous mode with only the receive function active. When set to false, + the channel operates in synchronous mode with both transmit and receive functions active. + + period-sync-pulse-us: + type: int + description: | + Specifies the period of the internally generated synchronization pulses, measured in + microseconds (us). This value determines the length of each synchronization pulse used + in the system. + + pulse-width-0-us: + type: int + description: | + Specifies the duration of the pulse width for a data bit value '0', measured in + microseconds (us). + + pulse-width-1-us: + type: int + description: | + Specifies the duration of the pulse width for a data bit value '1', measured in + microseconds (us). + + decoder-start-offset-us: + type: int + description: | + Specifies the duration for which the Manchester decoder remains inactive after the + falling edge of a synchronization pulse, measured in microseconds (us). This value + determines the delay before the decoder starts processing incoming signals again. + + tx-frame: + type: string + enum: + - "short-31-1s" + - "short-5-0s" + - "long-31-1s" + - "long-5-0s" + - "x-long-31-1s" + - "x-long-frame-5-0s" + - "xx-long-frame" + - "non-standard-frame" + description: | + Specifies the transmitter mode. Each mode defines the frame length and + the start condition for data transmission: + - short-31-1s: Short Frame (V1.3) with at least 31 consecutive '1' bits + as the start condition + - short-5-0s: Short Frame (V1.3) with at least 5 consecutive '0' bits + as the start condition + - long-31-1s: Long Frame (V1.3) with at least 31 consecutive '1' bits + as the start condition + - long-5-0s: Long Frame (V1.3) with at least 5 consecutive '0' bits + as the start condition + - x-long-31-1s: X-Long Frame (V1.3) with at least 31 consecutive '1' bits + as the start condition + - x-long-5-0s: X-Long Frame (V1.3) with at least 5 consecutive '0' bits + as the start condition + - xx-long: XX-Long (V2.0) + - non-standard: Non Standard Length + + rx-bitrate-kbps: + type: int + required: true + enum: + - 125 + - 189 + description: | + Selects the receive message bitrate in kbps. This setting determines the speed at + which data is received. + + child-binding: + description: | + Each child node defines the configuration of a channel RX slot + + properties: + reg: + type: int + required: true + description: Channel RX slot identifier. + + duration-us: + type: int + required: true + description: | + Specifies the duration of a slot, starting from the rising edge of the + timing synchronization pulse and ending at the final slot. + + start-offset-us: + type: int + required: true + description: | + Specifies the time offset at which the slot should start, measured from the + rising edge of the timing synchronization pulse. + + data-length: + type: int + required: true + description: | + Specifies the number of bits in a slot, with valid lengths ranging from 8 to 28 bits. + + data-msb-first: + type: boolean + description: | + Specifies the endianness type for data slot. Set to 1 when data is interpreted with the + Most Significant Bit (MSB) first. + + has-smc: + type: boolean + description: | + Specifies whether data slot has Serial Messaging Channel (SMC) field. Set to 1 when + the bit (M0, M1) SMC is present in the Rx Message. + + has-parity: + type: boolean + description: | + Specifies whether data slot has parity field. Set to 1 when the Parity field is + present in the Rx Message; otherwise, the CRC field is present. diff --git a/include/zephyr/drivers/psi5/psi5.h b/include/zephyr/drivers/psi5/psi5.h new file mode 100644 index 00000000000..a5961469aef --- /dev/null +++ b/include/zephyr/drivers/psi5/psi5.h @@ -0,0 +1,278 @@ +/* + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Peripheral Sensor Interface (PSI5) driver API. + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_PSI5_H_ +#define ZEPHYR_INCLUDE_DRIVERS_PSI5_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief PSI5 Interface + * @defgroup psi5_interface PSI5 Interface + * @since 4.2 + * @version 0.1.0 + * @ingroup io_interfaces + * @{ + */ + +/** + * @brief PSI5 frame type + */ +enum psi5_frame_type { + /** Serial message frame with 4-bit message ID */ + PSI5_SERIAL_FRAME_4_BIT_ID, + /** Serial message frame with 8-bit message ID */ + PSI5_SERIAL_FRAME_8_BIT_ID, + /** Data frame */ + PSI5_DATA_FRAME +}; + +/** + * @brief PSI5 frame structure + */ +struct psi5_frame { + /** Type of PSI5 frame */ + enum psi5_frame_type type; + + union { + /** Message data */ + uint32_t data; + + /** + * @brief Serial message + */ + struct { + /** Serial message ID */ + uint8_t id; + /** Serial message data */ + uint16_t data; + } serial; + }; + + /** Timestamp of when the frame was captured */ + uint32_t timestamp; + /** CRC checksum for message integrity validation */ + uint8_t crc; + /** Slot Number */ + uint8_t slot_number; +}; + +/** + * @brief Defines the application callback handler function signature for sending. + * + * @param dev Pointer to the device structure for the driver instance. + * @param channel The hardware channel of the driver instance. + * @param status PSI5 status (0: transmission completed successfully, + * -EIO: transmission error occurred). + * @param user_data User data provided when the frame was sent. + */ +typedef void (*psi5_tx_callback_t)(const struct device *dev, uint8_t channel, int status, + void *user_data); + +/** + * @brief Defines the application callback handler function signature for receiving frame. + * + * @param dev Pointer to the device structure for the driver instance. + * @param channel The hardware channel of the driver instance. + * @param num_frame Number of received frame. + * @param user_data User data provided when receiving frame. + */ +typedef void (*psi5_rx_frame_callback_t)(const struct device *dev, uint8_t channel, + uint32_t num_frame, void *user_data); + +/** @cond INTERNAL_HIDDEN */ + +/** + * @brief Callback API upon starting sync PSI5 + * See @a psi5_start_sync() for argument description + */ +typedef int (*psi5_start_sync_t)(const struct device *dev, uint8_t channel); + +/** + * @brief Callback API upon stopping sync PSI5 + * See @a psi5_stop_sync() for argument description + */ +typedef int (*psi5_stop_sync_t)(const struct device *dev, uint8_t channel); + +/** + * @brief Callback API upon sending PSI5 frame + * See @a psi5_send() for argument description + */ +typedef int (*psi5_send_t)(const struct device *dev, uint8_t channel, const uint64_t data, + k_timeout_t timeout, psi5_tx_callback_t callback, void *user_data); + +/** + * @brief Configuration structure for RX callback + */ +struct psi5_rx_callback_config { + /** Callback function invoked on frame reception */ + psi5_rx_frame_callback_t callback; + /** Pointer to the buffer for storing received frames */ + struct psi5_frame *frame; + /** Maximum number of frames to store */ + uint32_t max_num_frame; + /** Pointer to user data passed to the callback */ + void *user_data; +}; + +/** + * @brief Composite configuration structure for RX callback registration + */ +struct psi5_rx_callback_configs { + /** Configuration for the serial message callback */ + struct psi5_rx_callback_config *serial_frame; + /** Configuration for the data message callback */ + struct psi5_rx_callback_config *data_frame; +}; + +/** + * @brief Callback API upon adding RX callback + * See @a psi5_register_callback() for argument description + */ +typedef int (*psi5_register_callback_t)(const struct device *dev, uint8_t channel, + struct psi5_rx_callback_configs callback_configs); + +__subsystem struct psi5_driver_api { + psi5_start_sync_t start_sync; + psi5_stop_sync_t stop_sync; + psi5_send_t send; + psi5_register_callback_t register_callback; +}; + +/** @endcond */ + +/** + * @brief Start the sync pulse generator on a specific channel + * + * @param dev Pointer to the device structure for the driver instance. + * @param channel The hardware channel of the driver instance. + * @retval 0 successful. + * @retval -EINVAL invalid channel. + * @retval -EALREADY device is already started. + * @retval -EIO general input/output error, failed to start device. + */ +__syscall int psi5_start_sync(const struct device *dev, uint8_t channel); + +static inline int z_impl_psi5_start_sync(const struct device *dev, uint8_t channel) +{ + const struct psi5_driver_api *api = (const struct psi5_driver_api *)dev->api; + + if (api->start_sync) { + return api->start_sync(dev, channel); + } + + return -ENOSYS; +} + +/** + * @brief Stop the sync pulse generator on a specific channel + * + * @param dev Pointer to the device structure for the driver instance. + * @param channel The hardware channel of the driver instance. + * @retval 0 successful. + * @retval -EINVAL invalid channel. + * @retval -EALREADY device is already started. + * @retval -EIO general input/output error, failed to stop device. + */ +__syscall int psi5_stop_sync(const struct device *dev, uint8_t channel); + +static inline int z_impl_psi5_stop_sync(const struct device *dev, uint8_t channel) +{ + const struct psi5_driver_api *api = (const struct psi5_driver_api *)dev->api; + + if (api->stop_sync) { + return api->stop_sync(dev, channel); + } + + return -ENOSYS; +} + +/** + * @brief Transmitting PSI5 data on a specific channel + * + * The channel must be configured to synchronous mode and can only begin transmission after + * the sync pulse generator has started. + * + * @param dev Pointer to the device structure for the driver instance. + * @param channel The hardware channel of the driver instance. + * @param data PSI5 data to transmit. + * @param timeout Timeout waiting for ready to transmit new data. + * @param callback Optional callback for when the frame was sent or a + * transmission error occurred. If ``NULL``, this function is + * blocking until frame is sent. + * @param user_data User data to pass to callback function. + * + * @retval 0 successful. + * @retval -EINVAL invalid channel. + * @retval -ENOTSUP unsupported parameter was passed to the function. + * @retval -ENETDOWN stopped state. + * @retval -EIO general transmit error occurred. + * @retval -EAGAIN timeout. + */ +__syscall int psi5_send(const struct device *dev, uint8_t channel, const uint64_t data, + k_timeout_t timeout, psi5_tx_callback_t callback, void *user_data); + +static inline int z_impl_psi5_send(const struct device *dev, uint8_t channel, const uint64_t data, + k_timeout_t timeout, psi5_tx_callback_t callback, + void *user_data) +{ + const struct psi5_driver_api *api = (const struct psi5_driver_api *)dev->api; + + if (api->send) { + return api->send(dev, channel, data, timeout, callback, user_data); + } + + return -ENOSYS; +} + +/** + * @brief Add a callback function to handle messages received for a specific channel + * + * The callback must be registered before the sync pulse generator started when the channel + * is configured to synchronous mode. + * + * @param dev Pointer to the device structure for the driver instance. + * @param channel The hardware channel of the driver instance. + * @param callback_configs The callback configurations. + * @retval 0 successful. + * @retval -EINVAL invalid channel. + */ +__syscall int psi5_register_callback(const struct device *dev, uint8_t channel, + struct psi5_rx_callback_configs callback_configs); + +static inline int z_impl_psi5_register_callback(const struct device *dev, uint8_t channel, + struct psi5_rx_callback_configs callback_configs) +{ + const struct psi5_driver_api *api = (const struct psi5_driver_api *)dev->api; + + if (api->register_callback) { + return api->register_callback(dev, channel, callback_configs); + } + + return -ENOSYS; +} + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#include + +#endif /* ZEPHYR_INCLUDE_DRIVERS_PSI5_H_ */ diff --git a/west.yml b/west.yml index 800de58d550..fe8253bfa2f 100644 --- a/west.yml +++ b/west.yml @@ -210,7 +210,7 @@ manifest: groups: - hal - name: hal_nxp - revision: fc8aa27bba7ad1f8b98c04f991dae65ba38ec165 + revision: 7a52cbb7cb56db3a276cbd617db3ea7cc3435d12 path: modules/hal/nxp groups: - hal