diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index 50f64359a391..2f75aae5874e 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -31,6 +31,7 @@ add_subdirectory_ifdef(CONFIG_COUNTER counter) add_subdirectory_ifdef(CONFIG_CRYPTO crypto) add_subdirectory_ifdef(CONFIG_DAC dac) add_subdirectory_ifdef(CONFIG_DAI dai) +add_subdirectory_ifdef(CONFIG_DALI dali) add_subdirectory_ifdef(CONFIG_DISPLAY display) add_subdirectory_ifdef(CONFIG_DMA dma) add_subdirectory_ifdef(CONFIG_DP_DRIVER dp) diff --git a/drivers/Kconfig b/drivers/Kconfig index 401220c49177..d68f7f915cf0 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -23,6 +23,7 @@ source "drivers/counter/Kconfig" source "drivers/crypto/Kconfig" source "drivers/dac/Kconfig" source "drivers/dai/Kconfig" +source "drivers/dali/Kconfig" source "drivers/disk/Kconfig" source "drivers/display/Kconfig" source "drivers/dma/Kconfig" diff --git a/drivers/dali/CMakeLists.txt b/drivers/dali/CMakeLists.txt new file mode 100644 index 000000000000..f130b586e451 --- /dev/null +++ b/drivers/dali/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright (c) 2025 by Sven Hädrich +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() +# zephyr-keep-sorted-start +zephyr_library_sources_ifdef(CONFIG_DALI_LPC11U6X dali_lpc11u6x.c) +zephyr_library_sources_ifdef(CONFIG_DALI_PWM dali_pwm.c) +# zephyr-keep-sorted-stop diff --git a/drivers/dali/Kconfig b/drivers/dali/Kconfig new file mode 100644 index 000000000000..6518c2c3352f --- /dev/null +++ b/drivers/dali/Kconfig @@ -0,0 +1,26 @@ +# DALI configuration options + +# Copyright (c) 2025 by Sven Hädrich +# SPDX-License-Identifier: Apache-2.0 + +config DALI + bool "Digital Addressable Lighting Interface (DALI) drivers" + help + Enable generic support for DALI bus (IEC 62386). + +if DALI + +config DALI_MAX_FRAMES_IN_QUEUE + int "Max frames in DALI recv queue" + default 3 + range 1 16 + help + How many frames should be buffered in the recv queue + +source "drivers/dali/Kconfig.lpc11u6x" +source "drivers/dali/Kconfig.pwm" + +module = DALI_LOW_LEVEL +module-str = dali_low_level +source "subsys/logging/Kconfig.template.log_config" +endif # DALI \ No newline at end of file diff --git a/drivers/dali/Kconfig.lpc11u6x b/drivers/dali/Kconfig.lpc11u6x new file mode 100644 index 000000000000..78e3cf2c5610 --- /dev/null +++ b/drivers/dali/Kconfig.lpc11u6x @@ -0,0 +1,25 @@ +# LPC11U6x DALI configuration options + +# Copyright (c) 2025 by Sven Hädrich +# SPDX-License-Identifier: Apache-2.0 + +config DALI_LPC11U6X + bool "LPC11U6x DALI driver" + default y + depends on DT_HAS_NXP_DALI_LPC11U6X_ENABLED + help + Enable the DALI driver for LPC11U6X MCUs. + +config DALI_LPC11U6X_PRIORITY + int "DALI workqueue priority" + depends on DT_HAS_NXP_DALI_LPC11U6X_ENABLED + default 5 + help + The priority of the workqueue used for the DALI driver. + +config DALI_LPC11U6X_STACK_SIZE + int "DALI driver workqueue thread stack size" + depends on DT_HAS_NXP_DALI_LPC11U6X_ENABLED + default 768 + help + The thread stack size. diff --git a/drivers/dali/Kconfig.pwm b/drivers/dali/Kconfig.pwm new file mode 100644 index 000000000000..b0fb0b60f60b --- /dev/null +++ b/drivers/dali/Kconfig.pwm @@ -0,0 +1,43 @@ +# PWM DALI configuration options + +# Copyright (c) 2025 by Sven Hädrich +# SPDX-License-Identifier: Apache-2.0 + +config DALI_PWM + bool "Generic PWM DALI driver" + default y + depends on DT_HAS_ZEPHYR_DALI_PWM_ENABLED + select PWM + select PWM_CAPTURE + help + Enable DALI driver for generic PWM MCUs. + +if DALI_PWM + +choice + prompt "Thread for sending out" + default DALI_PWM_OWN_THREAD + +config DALI_PWM_GLOBAL_THREAD + bool "Use global thread" + +config DALI_PWM_OWN_THREAD + bool "Use own thread" + +endchoice + +config DALI_PWM_THREAD_PRIORITY + int "DALI send own thread priority" + depends on DALI_PWM_OWN_THREAD + default 10 + help + The priority of the thread used for sending DALI frames. + +config DALI_PWM_THREAD_STACK_SIZE + int "DALI send own thread stack size" + depends on DALI_PWM_OWN_THREAD + default 1024 + help + The thread stack size. + +endif # DALI_PWM diff --git a/drivers/dali/dali_lpc11u6x.c b/drivers/dali/dali_lpc11u6x.c new file mode 100644 index 000000000000..435b7a602862 --- /dev/null +++ b/drivers/dali/dali_lpc11u6x.c @@ -0,0 +1,1184 @@ +/* + * Copyright (c) 2025 by Sven Hädrich + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT nxp_dali_lpc11u6x + +#include /* api */ + +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(dali_low_level, CONFIG_DALI_LOW_LEVEL_LOG_LEVEL); + +#include "lpc11u6x.h" /* chip register definitions */ +#include "timings.h" /* timing constants from DALI standard */ + +#define DALI_TIMER_RATE_HZ (1000000U) +#define DALI_RX_PORT (0U) +#define DALI_RX_PIN (12U) +#define DALI_RX_BIT (1U << DALI_RX_PIN) +#define DALI_TX_PORT (0U) +#define DALI_TX_PIN (11U) +#define DALI_TX_BIT (1U << DALI_TX_PIN) +#define GREY_AREA_BITTIMING_US (18U) +#define GREY_AREA_INTERFRAME_US (800U) +#define DALI_TX_IDLE false +#define DALI_TX_ACTIVE true + +#define DALI_MAX_DATA_LENGTH (32U) +#define COUNT_ARRAY_SIZE \ + (2U + DALI_MAX_DATA_LENGTH * 2U + 1U) /* start bit, 32 data bits, 1 stop bit */ + +#define EXTEND_CORRUPT_PHASE 2 +#define TX_CORRUPT_BIT_US ((DALI_TX_CORRUPT_BIT_MAX_US + DALI_TX_CORRUPT_BIT_MIN_US) / 2) +#define TX_BREAK_US ((DALI_TX_BREAK_MAX_US + DALI_TX_BREAK_MIN_US) / 2) + +static struct k_work_q dali_work_queue; +static K_KERNEL_STACK_DEFINE(dali_work_queue_stack, CONFIG_DALI_LPC11U6X_STACK_SIZE); + +/* states for rx state machine */ +enum rx_state { + IDLE, + START_BIT_START, + START_BIT_INSIDE, + DATA_BIT_START, + DATA_BIT_INSIDE, + ERROR_IN_FRAME, + STOP_TRANSMISSION, + DESTROY_FRAME, + BUS_LOW, + BUS_FAILURE_DETECT, + TRANSMIT_BACKFRAME, + STOPBIT_BACKFRAME, +}; + +/* rx counter events */ +enum rx_counter_event { + CAPTURE, + STOPBIT, + PRIORITY, + QUERY, +}; + +/* see IEC 62386-101:2022 Table 22 - Multi-master transmitter settling time values */ +static const uint32_t settling_time_us[] = { + (DALI_TX_BACKWARD_INTERFRAME_MIN_US + (GREY_AREA_INTERFRAME_US / 2)), + (DALI_TX_PRIO_1_INTERFRAME_MIN_US + GREY_AREA_INTERFRAME_US), + (DALI_TX_PRIO_2_INTERFRAME_MIN_US + GREY_AREA_INTERFRAME_US), + (DALI_TX_PRIO_3_INTERFRAME_MIN_US + GREY_AREA_INTERFRAME_US), + (DALI_TX_PRIO_4_INTERFRAME_MIN_US + GREY_AREA_INTERFRAME_US), + (DALI_TX_PRIO_5_INTERFRAME_MIN_US + GREY_AREA_INTERFRAME_US), + (DALI_TX_STOP_BIT_US + GREY_AREA_INTERFRAME_US)}; + +struct dali_tx_slot { + uint32_t count[COUNT_ARRAY_SIZE]; + uint_fast8_t index_next; + uint_fast8_t index_max; + bool state_now; + bool is_query; + uint32_t inter_frame_idle; +}; + +struct dali_lpc11u6x_config { + int32_t tx_rise_fall_delta_us; + int32_t rx_rise_fall_delta_us; + int32_t tx_rx_propagation_min_us; + int32_t tx_rx_propagation_max_us; + void (*irq_config_function)(void); +}; + +struct dali_lpc11u6x_data { + const struct dali_lpc11u6x_config *config; + struct dali_tx_slot forward; + struct dali_tx_slot backward; + struct dali_tx_slot *active; + enum rx_state rx_status; + uint32_t last_edge_count; + uint32_t last_full_frame_count; + uint32_t edge_count; + bool last_data_bit; + struct k_work rx_work; + enum rx_counter_event rx_event; + struct k_msgq rx_queue; + char rx_buffer[CONFIG_DALI_MAX_FRAMES_IN_QUEUE * sizeof(struct dali_frame)]; + uint32_t rx_data; + uint32_t rx_timestamp; + uint8_t rx_frame_length; + uint32_t rx_last_timestamp; + uint32_t rx_last_payload; + uint32_t rx_last_frame_length; + uint32_t tx_count_on_capture; +}; + +enum board_toggle { + NOTHING, + DISABLE_TOGGLE +}; + +static void dali_lpc11u6x_init_peripheral_clock(void) +{ + LPC_SYSCON->SYSAHBCLKCTRL |= (SYSAHBCLKCTRL_CT32B0 | SYSAHBCLKCTRL_CT32B1); +} + +static void dali_lpc11u6x_init_io_pins(void) +{ + LPC_GPIO_PORT->dir[DALI_TX_PORT] |= DALI_TX_BIT; + LPC_IOCON->pio0_12 = + (IOCON_DAPIN_FUNC_MASK & IOCON_PIN0_12_FUNC_PIO) << IOCON_DAPIN_FUNC_SHIFT | + (IOCON_DAPIN_MODE_MASK & IOCON_DAPIN_MODE_PULLUP) << IOCON_DAPIN_MODE_SHIFT | + (IOCON_DAPIN_SMODE_MASK & 3U) << IOCON_DAPIN_SMODE_SHIFT | + (IOCON_DAPIN_CLKDIV_MASK & 6U) << IOCON_DAPIN_CLKDIV_SHIFT; +} + +static bool dali_lpc11u6x_get_rx_pin(void) +{ + return !(LPC_GPIO_PORT->pin[DALI_RX_PORT] & DALI_RX_BIT); +} + +static void dali_lpc11u6x_set_tx_counter(bool state) +{ + LPC_IOCON->pio0_11 = + (IOCON_DAPIN_FUNC_MASK & IOCON_PIN0_11_FUNC_PIO) << IOCON_DAPIN_FUNC_SHIFT | + (IOCON_DAPIN_MODE_MASK & IOCON_DAPIN_MODE_PULLDOWN) << IOCON_DAPIN_MODE_SHIFT | + (IOCON_DAPIN_ADMODE); + if (state) { + LPC_GPIO_PORT->set[DALI_TX_PORT] |= DALI_TX_BIT; + } else { + LPC_GPIO_PORT->clr[DALI_TX_PORT] |= DALI_TX_BIT; + } +} + +static void dali_lpc11u6x_stop_tx_counter(void) +{ + LPC_CT32B0->mcr = (LPC_CT32B0->mcr & + ~(CT32_MCR_MR0I | CT32_MCR_MR3I | CT32_MCR_MR3R | CT32_MCR_MR3S)); + LPC_CT32B0->emr = (CT32_EMR_EM2 | (CT32_EMR_EMCTR_NOTHING << CT32_EMR_EMC2_SHIFT)); +} + +static void dali_lpc11u6x_set_next_match_tx_counter(uint32_t count, enum board_toggle toggle) +{ + LPC_CT32B0->mr3 = count; + if (toggle == DISABLE_TOGGLE) { + LPC_CT32B0->emr &= ~(CT32_EMR_EMCTR_TOGGLE << CT32_EMR_EMC3_SHIFT); + } +} + +static uint32_t dali_lpc11u6x_get_tx_counter(void) +{ + return LPC_CT32B0->tc; +} + +static void dali_lpc11u6x_set_collision_counter(uint32_t count) +{ + LPC_CT32B0->mr0 = count; +} + +static void dali_lpc11u6x_init_tx_counter(uint32_t count, bool check_collision) +{ + LPC_CT32B0->tcr = CT32_TCR_CRST; + dali_lpc11u6x_stop_tx_counter(); + dali_lpc11u6x_set_next_match_tx_counter(count, NOTHING); + /* set prescaler to base rate */ + LPC_CT32B0->pr = (CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC / DALI_TIMER_RATE_HZ) - 1; + /* set timer mode */ + LPC_CT32B0->ctcr = 0; + /* on MR3 match: IRQ */ + LPC_CT32B0->mcr = (LPC_CT32B0->mcr & ~(CT32_MCR_MR3I | CT32_MCR_MR3R | CT32_MCR_MR3S)) | + (CT32_MCR_MR3I); + /* for collision checking enable MR0 */ + if (check_collision) { + LPC_CT32B0->mcr |= CT32_MCR_MR0I; + } else { + LPC_CT32B0->mcr &= ~CT32_MCR_MR0I; + } + /* on MR3 match: toggle output - start with DALI active */ + LPC_CT32B0->emr = CT32_EMR_EM3 | (CT32_EMR_EMCTR_TOGGLE << CT32_EMR_EMC3_SHIFT); + /* outputs are controlled by EMx */ + LPC_CT32B0->pwmc = 0; + /* pin function: CT32B0_MAT3 */ + /* function mode: no pull up/down resistors */ + /* hysteresis disabled */ + /* standard gpio */ + LPC_IOCON->pio0_11 = + (IOCON_DAPIN_FUNC_MASK & IOCON_PIN0_11_FUNC_MAT3) << IOCON_DAPIN_FUNC_SHIFT | + (IOCON_DAPIN_MODE_MASK & IOCON_DAPIN_MODE_PULLDOWN) << IOCON_DAPIN_MODE_SHIFT | + (IOCON_DAPIN_ADMODE); + /* start timer */ + LPC_CT32B0->tcr = CT32_TCR_CEN; +} + +static uint32_t dali_lpc116x_get_rx_counter(void) +{ + return LPC_CT32B1->tc; +} + +static uint32_t dali_lpc11u6x_get_rx_capture(void) +{ + return LPC_CT32B1->cr0; +} + +static void dali_lpc11u6x_set_rx_counter(enum rx_counter_event event, uint32_t match_count) +{ + switch (event) { + case STOPBIT: + LPC_CT32B1->mr0 = match_count; + break; + case PRIORITY: + LPC_CT32B1->mr1 = match_count; + break; + case QUERY: + LPC_CT32B1->mr2 = match_count; + break; + default: + __ASSERT(false, "unexpected event code"); + } +} + +static void dali_lpc11u6x_enable_rx_counter(enum rx_counter_event event, bool enable) +{ + switch (event) { + case STOPBIT: + if (enable) { + LPC_CT32B1->mcr |= (CT32_MCR_MR0I); + } else { + LPC_CT32B1->mcr &= ~(CT32_MCR_MR0I); + } + break; + case PRIORITY: + if (enable) { + LPC_CT32B1->mcr |= (CT32_MCR_MR1I); + } else { + LPC_CT32B1->mcr &= ~(CT32_MCR_MR1I); + } + break; + case QUERY: + if (enable) { + LPC_CT32B1->mcr |= (CT32_MCR_MR2I); + } else { + LPC_CT32B1->mcr &= ~(CT32_MCR_MR2I); + } + break; + default: + __ASSERT(false, "unexpected"); + } +} + +void dali_lpc11u6x_init_rx_counter(void) +{ + LPC_CT32B1->tcr = CT32_TCR_CRST; + /* set prescaler to base rate */ + LPC_CT32B1->pr = (CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC / DALI_TIMER_RATE_HZ) - 1; + /* set timer mode */ + LPC_CT32B1->ctcr = 0; + /* capture both edges, trigger IRQ */ + LPC_CT32B1->ccr = (CT32_CCR_CAP0FE | CT32_CCR_CAP0RE | CT32_CCR_CAP0I); + /* disable event matches */ + dali_lpc11u6x_enable_rx_counter(STOPBIT, false); + dali_lpc11u6x_enable_rx_counter(PRIORITY, false); + dali_lpc11u6x_enable_rx_counter(QUERY, false); + /* pin function: CT32B1_CAP0 */ + /* function mode: enable pull up resistor */ + /* hysteresis disabled */ + /* digital function mode, standard gpio */ + LPC_IOCON->pio0_12 = + (IOCON_DAPIN_FUNC_MASK & IOCON_PIN0_12_FUNC_CAP0) << IOCON_DAPIN_FUNC_SHIFT | + (IOCON_DAPIN_MODE_MASK & IOCON_DAPIN_MODE_PULLDOWN) << IOCON_DAPIN_MODE_SHIFT | + (IOCON_DAPIN_ADMODE); + /* start timer */ + LPC_CT32B1->tcr = CT32_TCR_CEN; +} + +static void dali_lpc11u6x_reset_tx_slot(struct dali_tx_slot *slot) +{ + __ASSERT(slot, "invalid tx slot"); + + *slot = (struct dali_tx_slot){ + .state_now = true, + }; +} + +static void dali_lpc11u6x_reset_tx_all_slots(struct dali_lpc11u6x_data *data) +{ + dali_lpc11u6x_reset_tx_slot(&data->forward); + dali_lpc11u6x_reset_tx_slot(&data->backward); + data->active = 0; +} + +static bool dali_lpc11u6x_is_tx_slot_empty(const struct dali_tx_slot *slot) +{ + return slot->index_max == 0; +} + +static void dali_lpc11u6x_add_signal_phase(struct dali_tx_slot *slot, uint32_t duration_us, + bool change_last_phase) +{ + __ASSERT((slot->index_max < COUNT_ARRAY_SIZE), "PWM pattern does not fit into buffer"); + + if (change_last_phase) { + slot->index_max--; + } + uint32_t count_now = + (slot->index_max) ? (slot->count[slot->index_max - 1] + duration_us) : duration_us; + slot->count[slot->index_max++] = count_now; +} + +static void dali_lpc11u6x_add_bit(const struct device *dev, struct dali_tx_slot *slot, bool value) +{ + const struct dali_lpc11u6x_config *config = dev->config; + + uint32_t phase_one; + uint32_t phase_two; + bool change_previous = false; + + if (slot->state_now == value) { + if (slot->state_now) { + phase_one = DALI_TX_HALF_BIT_US + config->tx_rise_fall_delta_us; + phase_two = DALI_TX_HALF_BIT_US - config->tx_rise_fall_delta_us; + } else { + phase_one = DALI_TX_HALF_BIT_US - config->tx_rise_fall_delta_us; + phase_two = DALI_TX_HALF_BIT_US + config->tx_rise_fall_delta_us; + } + } else { + change_previous = true; + if (slot->state_now) { + phase_one = DALI_TX_FULL_BIT_US - config->tx_rise_fall_delta_us; + phase_two = DALI_TX_HALF_BIT_US + config->tx_rise_fall_delta_us; + } else { + phase_one = DALI_TX_FULL_BIT_US + config->tx_rise_fall_delta_us; + phase_two = DALI_TX_HALF_BIT_US - config->tx_rise_fall_delta_us; + } + } + dali_lpc11u6x_add_signal_phase(slot, phase_one, change_previous); + dali_lpc11u6x_add_signal_phase(slot, phase_two, false); + slot->state_now = value; +} + +static void dali_lpc11u6x_add_stop_condition(struct dali_tx_slot *slot) +{ + if (slot->state_now) { + dali_lpc11u6x_add_signal_phase(slot, DALI_TX_STOP_BIT_US, true); + slot->index_max--; + } else { + dali_lpc11u6x_add_signal_phase(slot, DALI_TX_STOP_BIT_US, false); + slot->index_max--; + } +} + +static void dali_lpc11u6x_calculate_counts(const struct device *dev, struct dali_tx_slot *slot, + const struct dali_frame frame) +{ + uint_fast8_t length = 0; + + switch (frame.event_type) { + case DALI_FRAME_CORRUPT: + for (int_fast8_t i = 0; i < (2 * DALI_FRAME_BACKWARD_LENGTH); i++) { + if (i == EXTEND_CORRUPT_PHASE) { + dali_lpc11u6x_add_signal_phase(slot, TX_CORRUPT_BIT_US, false); + } else { + dali_lpc11u6x_add_signal_phase(slot, DALI_TX_HALF_BIT_US, false); + } + } + return; + case DALI_FRAME_BACKWARD: + length = DALI_FRAME_BACKWARD_LENGTH; + break; + case DALI_FRAME_GEAR: + length = DALI_FRAME_GEAR_LENGTH; + break; + case DALI_FRAME_DEVICE: + length = DALI_FRAME_DEVICE_LENGTH; + break; + default: + __ASSERT(false, "illegal event type"); + } + + if (length) { + /* add start bit */ + dali_lpc11u6x_add_bit(dev, slot, true); + /* add data bits */ + for (int_fast8_t i = (length - 1); i >= 0; i--) { + dali_lpc11u6x_add_bit(dev, slot, frame.data & (1 << i)); + } + dali_lpc11u6x_add_stop_condition(slot); + } +} + +static void dali_lpc11u6x_schedule_query(void) +{ + const uint32_t query_count = dali_lpc116x_get_rx_counter() + DALI_RX_FORWARD_BACK_MAX_US + + GREY_AREA_INTERFRAME_US; + dali_lpc11u6x_set_rx_counter(QUERY, query_count); + dali_lpc11u6x_enable_rx_counter(QUERY, true); +} + +static bool dali_lpc116x_is_forward_active(struct dali_lpc11u6x_data *data) +{ + return (data->active == &data->forward && data->active->index_next); +} + +static void dali_lpc11u6x_stop_tx(struct dali_lpc11u6x_data *data) +{ + if (dali_lpc116x_is_forward_active(data)) { + data->rx_status = STOP_TRANSMISSION; + dali_lpc11u6x_stop_tx_counter(); + dali_lpc11u6x_set_tx_counter(DALI_TX_IDLE); + } +} + +static void dali_lpc11u6x_destroy_frame(struct dali_lpc11u6x_data *data) +{ + if (dali_lpc116x_is_forward_active(data)) { + if (data->rx_status == DESTROY_FRAME) { + return; + } + if (data->rx_status != STOP_TRANSMISSION) { + dali_lpc11u6x_stop_tx_counter(); + dali_lpc11u6x_set_tx_counter(DALI_TX_ACTIVE); + } + data->rx_status = DESTROY_FRAME; + + /* use stopbit counter to time break condition */ + const uint32_t break_count = data->edge_count + TX_BREAK_US; + dali_lpc11u6x_set_rx_counter(STOPBIT, break_count); + dali_lpc11u6x_enable_rx_counter(STOPBIT, true); + } +} + +void dali_lpc11u6x_handle_tx_callback(const struct device *dev) +{ + struct dali_lpc11u6x_data *data = dev->data; + const struct dali_lpc11u6x_config *config = data->config; + struct dali_tx_slot *active = data->active; + + /* schedule collision check for current transition */ + if (dali_lpc116x_is_forward_active(data)) { + const uint32_t last_transition = active->count[active->index_next - 1]; + dali_lpc11u6x_set_collision_counter(last_transition + + config->tx_rx_propagation_min_us); + active->state_now = !active->state_now; + } + + /* schedule next level transition */ + if (active->index_next < active->index_max) { + const uint32_t next_transition = active->count[active->index_next++]; + dali_lpc11u6x_set_next_match_tx_counter(next_transition, NOTHING); + return; + } + + /* schedule the last transition */ + if (active->index_next == active->index_max) { + const uint32_t next_transition = active->count[active->index_next++]; + dali_lpc11u6x_set_next_match_tx_counter(next_transition, DISABLE_TOGGLE); + if (data->rx_status == TRANSMIT_BACKFRAME) { + data->rx_status = STOPBIT_BACKFRAME; + } + return; + } + + /* activities at end of frame */ + dali_lpc11u6x_set_tx_counter(DALI_TX_IDLE); + dali_lpc11u6x_stop_tx_counter(); + if (active->is_query) { + dali_lpc11u6x_schedule_query(); + } + dali_lpc11u6x_reset_tx_slot(active); + data->active = 0; +} + +void dali_lpc11u6x_handle_collision_callback(const struct device *dev) +{ + struct dali_lpc11u6x_data *data = dev->data; + struct dali_tx_slot *active = data->active; + + if (dali_lpc11u6x_get_rx_pin() == active->state_now) { + dali_lpc11u6x_stop_tx(data); + LOG_ERR("unexpected bus state while sending period %d -- stop transmission", + active->index_next); + } +} + +static void dali_lpc11u6x_handle_tx_irq(const struct device *dev) +{ + if (LPC_CT32B0->ir & CT32_IR_MR3INT) { + LPC_CT32B0->ir = CT32_IR_MR3INT; + dali_lpc11u6x_handle_tx_callback(dev); + } + if (LPC_CT32B0->ir & CT32_IR_MR0INT) { + LPC_CT32B0->ir = CT32_IR_MR0INT; + dali_lpc11u6x_handle_collision_callback(dev); + } +} + +static void dali_lpc11u6x_start_tx(struct dali_lpc11u6x_data *data) +{ + if (data->active) { + data->active->index_next = 1; + data->active->state_now = true; + dali_lpc11u6x_init_tx_counter(data->active->count[0], + dali_lpc116x_is_forward_active(data)); + } +} + +static void dali_lpc11u6x_schedule_tx(struct dali_lpc11u6x_data *data) +{ + /* select the frame to send - backward frame is dominant */ + if (!dali_lpc11u6x_is_tx_slot_empty(&data->forward)) { + data->active = &data->forward; + } + if (!dali_lpc11u6x_is_tx_slot_empty(&data->backward)) { + data->active = &data->backward; + } + + uint32_t start_send_rx_count = data->active->inter_frame_idle; + if (data->rx_status == TRANSMIT_BACKFRAME) { + start_send_rx_count += data->last_full_frame_count; + } else { + start_send_rx_count += data->last_edge_count; + } + if (dali_lpc116x_get_rx_counter() > start_send_rx_count) { + dali_lpc11u6x_enable_rx_counter(PRIORITY, false); + dali_lpc11u6x_start_tx(data); + } else { + dali_lpc11u6x_set_rx_counter(PRIORITY, start_send_rx_count); + dali_lpc11u6x_enable_rx_counter(PRIORITY, true); + } +} + +static bool dali_lpc11u6x_is_rx_twice(struct dali_lpc11u6x_data *data) +{ + const uint32_t frame_duration_us = (data->rx_frame_length + 1) * DALI_TX_FULL_BIT_US; + const uint32_t time_difference_us = + data->rx_timestamp - data->rx_last_timestamp - frame_duration_us; + const bool is_data_identical = (data->rx_data == data->rx_last_payload && + data->rx_frame_length == data->rx_last_frame_length); + + data->rx_last_timestamp = data->rx_timestamp; + data->rx_last_payload = data->rx_data; + data->rx_last_frame_length = data->rx_frame_length; + + if (time_difference_us > DALI_RX_TWICE_MAX_US + GREY_AREA_INTERFRAME_US) { + return false; + } + return is_data_identical; +} + +static void dali_lpc11u6x_reset_rx_twice(struct dali_lpc11u6x_data *data) +{ + data->rx_last_payload = 0; + data->rx_last_frame_length = 0; +} + +static void dali_lpc11u6x_finish_rx_frame(struct dali_lpc11u6x_data *data) +{ + struct dali_frame frame = { + .data = data->rx_data, + .event_type = DALI_EVENT_NONE, + }; + switch (data->rx_status) { + case START_BIT_START: + case START_BIT_INSIDE: + case DATA_BIT_START: + case DATA_BIT_INSIDE: + LOG_INF("{%08x:%02x %08x}", data->rx_timestamp, data->rx_frame_length, + data->rx_data); + switch (data->rx_frame_length) { + case DALI_FRAME_BACKWARD_LENGTH: + frame.event_type = DALI_FRAME_BACKWARD; + break; + case DALI_FRAME_GEAR_LENGTH: + data->last_full_frame_count = data->last_edge_count; + frame.event_type = dali_lpc11u6x_is_rx_twice(data) ? DALI_FRAME_GEAR_TWICE + : DALI_FRAME_GEAR; + break; + case DALI_FRAME_DEVICE_LENGTH: + data->last_full_frame_count = data->last_edge_count; + frame.event_type = dali_lpc11u6x_is_rx_twice(data) ? DALI_FRAME_DEVICE_TWICE + : DALI_FRAME_DEVICE; + break; + case DALI_FRAME_UPDATE_LENGTH: + dali_lpc11u6x_is_rx_twice(data); + data->last_full_frame_count = data->last_edge_count; + frame.event_type = DALI_FRAME_FIRMWARE; + break; + default: + LOG_INF("invalid frame length %d bits", data->rx_frame_length); + dali_lpc11u6x_reset_rx_twice(data); + frame.data = 0, frame.event_type = DALI_FRAME_CORRUPT; + } + data->rx_status = IDLE; + break; + case STOP_TRANSMISSION: + frame.data = 0; + frame.event_type = DALI_FRAME_CORRUPT; + dali_lpc11u6x_reset_rx_twice(data); + data->rx_status = IDLE; + /* re-schedule the current frame data */ + data->active->index_next = 0; + data->active->state_now = true; + data->active->inter_frame_idle = DALI_TX_RECOVER_MIN_US; + dali_lpc11u6x_schedule_tx(data); + break; + case BUS_FAILURE_DETECT: + LOG_INF("bus failure"); + frame.data = 0; + frame.event_type = DALI_EVENT_BUS_FAILURE; + dali_lpc11u6x_reset_rx_twice(data); + break; + case IDLE: + LOG_INF("bus idle"); + frame.data = 0; + frame.event_type = DALI_EVENT_BUS_IDLE; + dali_lpc11u6x_reset_rx_twice(data); + break; + case ERROR_IN_FRAME: + default: + LOG_INF("corrupt frame"); + frame.data = 0; + frame.event_type = DALI_FRAME_CORRUPT; + dali_lpc11u6x_reset_rx_twice(data); + data->rx_status = IDLE; + } + k_msgq_put(&data->rx_queue, &frame, K_NO_WAIT); +} + +static bool dali_lpc11u6x_is_valid_halfbit_timing(const uint32_t time_difference_us) +{ + if ((time_difference_us > DALI_RX_BIT_TIME_HALF_MAX_US + GREY_AREA_BITTIMING_US) || + (time_difference_us < DALI_RX_BIT_TIME_HALF_MIN_US - GREY_AREA_BITTIMING_US)) { + return false; + } + return true; +} + +static bool dali_lpc11u6x_is_valid_fullbit_timing(const uint32_t time_difference_us) +{ + if ((time_difference_us > DALI_RX_BIT_TIME_FULL_MAX_US + GREY_AREA_BITTIMING_US) || + (time_difference_us < DALI_RX_BIT_TIME_FULL_MIN_US - GREY_AREA_BITTIMING_US)) { + return false; + } + return true; +} + +static bool dali_lpc11u6x_is_destroy_start(const uint32_t time_difference_us) +{ + if ((time_difference_us > DALI_TX_DESTROY_1_MIN_US - GREY_AREA_BITTIMING_US) && + (time_difference_us < DALI_TX_DESTROY_1_MAX_US + GREY_AREA_BITTIMING_US)) { + return true; + } + if ((time_difference_us > DALI_TX_DESTROY_2_MIN_US + GREY_AREA_BITTIMING_US)) { + return true; + } + return false; +} + +static bool dali_lpc11u6x_is_destroy_inside(const uint32_t time_difference_us) +{ + if ((time_difference_us > DALI_TX_DESTROY_1_MIN_US - GREY_AREA_BITTIMING_US) && + (time_difference_us < DALI_TX_DESTROY_1_MAX_US + GREY_AREA_BITTIMING_US)) { + return true; + } + if ((time_difference_us > DALI_TX_DESTROY_2_MIN_US - GREY_AREA_BITTIMING_US) && + (time_difference_us < DALI_TX_DESTROY_2_MAX_US + GREY_AREA_BITTIMING_US)) { + return true; + } + if ((time_difference_us > DALI_TX_DESTROY_3_MIN_US - GREY_AREA_BITTIMING_US)) { + return true; + } + return false; +} + +static uint32_t dali_lpc11u6x_get_time_difference_us(const struct dali_lpc11u6x_data *data, + bool invert) +{ + const struct dali_lpc11u6x_config *config = data->config; + + const uint32_t time_difference_us = data->edge_count - data->last_edge_count; + if (data->last_data_bit ^ invert) { + return time_difference_us - config->rx_rise_fall_delta_us; + } + return time_difference_us + config->rx_rise_fall_delta_us; +} + +static void dali_lpc11u6x_set_status(struct dali_lpc11u6x_data *data, enum rx_state new_status) +{ + switch (data->rx_status) { + case ERROR_IN_FRAME: + case STOP_TRANSMISSION: + case DESTROY_FRAME: + return; + default: + data->rx_status = new_status; + } +} + +static void dali_lpc11u6x_add_bit_to_rx_data(struct dali_lpc11u6x_data *data) +{ + data->rx_data = (data->rx_data << 1U) | data->last_data_bit; + data->rx_frame_length++; + if (data->rx_frame_length > DALI_MAX_BIT_PER_FRAME) { + dali_lpc11u6x_set_status(data, ERROR_IN_FRAME); + } +} + +static void dali_lpc11u6x_check_start_timing(struct dali_lpc11u6x_data *data) +{ + const uint32_t time_difference_us = dali_lpc11u6x_get_time_difference_us(data, false); + LOG_DBG("start timing: %d us", time_difference_us); + if (dali_lpc116x_is_forward_active(data) && + dali_lpc11u6x_is_destroy_start(time_difference_us)) { + dali_lpc11u6x_destroy_frame(data); + if (data->rx_status == START_BIT_START) { + LOG_ERR("start bit collision, timing %d us, destroy frame", + time_difference_us); + } else { + LOG_ERR("data bit %d collision, timing %d us, destroy frame", + data->rx_frame_length, time_difference_us); + } + return; + } + if (!dali_lpc11u6x_is_valid_halfbit_timing(time_difference_us)) { + dali_lpc11u6x_set_status(data, ERROR_IN_FRAME); + if (data->rx_status == START_BIT_START) { + LOG_ERR("start bit timing %d us, corrupt frame", time_difference_us); + } else { + LOG_ERR("data bit %d timing %d us, corrupt frame", data->rx_frame_length, + time_difference_us); + } + return; + } + if (data->rx_status == DATA_BIT_START) { + dali_lpc11u6x_add_bit_to_rx_data(data); + } +} + +static enum rx_state dali_lpc11u6x_check_inside_timing(struct dali_lpc11u6x_data *data) +{ + uint32_t time_difference_us = dali_lpc11u6x_get_time_difference_us(data, true); + LOG_DBG("inside timing: %d us", time_difference_us); + if (dali_lpc116x_is_forward_active(data) && + dali_lpc11u6x_is_destroy_inside(time_difference_us)) { + dali_lpc11u6x_destroy_frame(data); + if (data->rx_status == START_BIT_INSIDE) { + LOG_ERR("inside start bit collision, timing %d us, destroy frame", + time_difference_us); + } else { + LOG_ERR("inside bit %d collision, timing %d us, destroy frame", + data->rx_frame_length, time_difference_us); + } + return DESTROY_FRAME; + } + if (dali_lpc11u6x_is_valid_halfbit_timing(time_difference_us)) { + return DATA_BIT_START; + } + if (dali_lpc11u6x_is_valid_fullbit_timing(time_difference_us)) { + data->last_data_bit = !data->last_data_bit; + dali_lpc11u6x_add_bit_to_rx_data(data); + return DATA_BIT_INSIDE; + } + if (data->rx_status == START_BIT_INSIDE) { + LOG_ERR("inside start bit timing error %d us", time_difference_us); + } else { + LOG_ERR("inside data bit %d timing error %d us", data->rx_frame_length, + time_difference_us); + } + return ERROR_IN_FRAME; +} + +static void dali_lpc11u6x_check_collision(struct dali_lpc11u6x_data *data) +{ + struct dali_tx_slot *active = data->active; + if (!dali_lpc116x_is_forward_active(data)) { + return; + } + const struct dali_lpc11u6x_config *config = data->config; + const uint32_t expected_count = active->count[active->index_next - 2]; + const int32_t delay = data->tx_count_on_capture - expected_count; + if (delay < 0 || delay > config->tx_rx_propagation_max_us) { + dali_lpc11u6x_stop_tx(data); + LOG_ERR("unexpected capture with delay of %d us while receiving bit %d, stop " + "transmission", + delay, data->rx_frame_length); + } +} + +static void dali_lpc11u6x_process_capture_event(struct dali_lpc11u6x_data *data) +{ + if (data->rx_status == STOP_TRANSMISSION) { + data->last_edge_count = dali_lpc116x_get_rx_counter(); + return; + } + + if (data->rx_status == DESTROY_FRAME) { + data->last_edge_count = dali_lpc116x_get_rx_counter(); + if (dali_lpc11u6x_get_rx_pin()) { + data->rx_status = IDLE; + + /* re-schedule the current frame data */ + data->active->index_next = 0; + data->active->state_now = true; + data->active->inter_frame_idle = DALI_TX_RECOVER_MIN_US; + dali_lpc11u6x_schedule_tx(data); + } + return; + } + + data->edge_count = dali_lpc11u6x_get_rx_capture(); + const struct dali_lpc11u6x_config *config = data->config; + const uint32_t stop_timeout_count = + data->edge_count + DALI_RX_BIT_TIME_STOP_US + 2 * config->rx_rise_fall_delta_us; + dali_lpc11u6x_set_rx_counter(STOPBIT, stop_timeout_count); + dali_lpc11u6x_enable_rx_counter(STOPBIT, true); + + if (data->rx_status == TRANSMIT_BACKFRAME || data->rx_status == STOPBIT_BACKFRAME) { + data->last_edge_count = data->edge_count; + return; + } + + switch (data->rx_status) { + case IDLE: + if (!dali_lpc11u6x_get_rx_pin()) { + dali_lpc11u6x_set_status(data, START_BIT_START); + data->last_data_bit = true; + data->rx_timestamp = dali_lpc116x_get_rx_counter(); + data->rx_data = 0; + data->rx_frame_length = 0; + dali_lpc11u6x_enable_rx_counter(QUERY, false); + dali_lpc11u6x_enable_rx_counter(PRIORITY, false); + } + break; + case START_BIT_START: + dali_lpc11u6x_check_collision(data); + dali_lpc11u6x_check_start_timing(data); + dali_lpc11u6x_set_status(data, START_BIT_INSIDE); + break; + case START_BIT_INSIDE: + dali_lpc11u6x_check_collision(data); + dali_lpc11u6x_set_status(data, dali_lpc11u6x_check_inside_timing(data)); + break; + case DATA_BIT_START: + dali_lpc11u6x_check_collision(data); + dali_lpc11u6x_check_start_timing(data); + dali_lpc11u6x_set_status(data, DATA_BIT_INSIDE); + break; + case DATA_BIT_INSIDE: + dali_lpc11u6x_check_collision(data); + dali_lpc11u6x_set_status(data, dali_lpc11u6x_check_inside_timing(data)); + break; + case STOPBIT_BACKFRAME: + case ERROR_IN_FRAME: + break; + case BUS_LOW: + if (dali_lpc11u6x_get_rx_pin()) { + dali_lpc11u6x_set_status(data, ERROR_IN_FRAME); + dali_lpc11u6x_finish_rx_frame(data); + } + break; + case BUS_FAILURE_DETECT: + if (dali_lpc11u6x_get_rx_pin()) { + data->rx_status = IDLE; + dali_lpc11u6x_finish_rx_frame(data); + } + break; + default: + __ASSERT(false, "invalid state"); + } + data->last_edge_count = data->edge_count; + /* if transmission pending: re-schedule the next transmission */ + if (data->active && data->active->index_next == 0) { + dali_lpc11u6x_schedule_tx(data); + } +} + +static void dali_lpc11u6x_restart_tx(struct dali_lpc11u6x_data *data) +{ + /* set the bus to idle */ + dali_lpc11u6x_set_tx_counter(DALI_TX_IDLE); + dali_lpc11u6x_enable_rx_counter(STOPBIT, false); + + /* push information into receive queue */ + struct dali_frame frame = { + .event_type = DALI_FRAME_CORRUPT, + }; + k_msgq_put(&data->rx_queue, &frame, K_NO_WAIT); + dali_lpc11u6x_reset_rx_twice(data); +} + +static void dali_lpc11u6x_process_stopbit_event(struct dali_lpc11u6x_data *data) +{ + /* this can happen with extensive long bus active periods */ + if (data->rx_status == TRANSMIT_BACKFRAME) { + /* re-start counter */ + const uint32_t stop_timeout_count = data->edge_count + DALI_RX_BIT_TIME_STOP_US + + 2 * data->config->rx_rise_fall_delta_us; + dali_lpc11u6x_set_rx_counter(STOPBIT, stop_timeout_count); + dali_lpc11u6x_enable_rx_counter(STOPBIT, true); + return; + } + + if (dali_lpc11u6x_get_rx_pin()) { + switch (data->rx_status) { + case IDLE: + case START_BIT_START: + case START_BIT_INSIDE: + case DATA_BIT_START: + case DATA_BIT_INSIDE: + case BUS_LOW: + case BUS_FAILURE_DETECT: + case ERROR_IN_FRAME: + case STOP_TRANSMISSION: + dali_lpc11u6x_finish_rx_frame(data); + return; + case STOPBIT_BACKFRAME: + data->rx_status = IDLE; + return; + default: + __ASSERT(false, "invalid state"); + return; + } + } + switch (data->rx_status) { + case DESTROY_FRAME: + dali_lpc11u6x_restart_tx(data); + return; + case BUS_LOW: + data->rx_status = BUS_FAILURE_DETECT; + dali_lpc11u6x_finish_rx_frame(data); + return; + case BUS_FAILURE_DETECT: + return; + default: + dali_lpc11u6x_set_rx_counter(STOPBIT, data->edge_count + DALI_FAILURE_CONDITION_US); + dali_lpc11u6x_enable_rx_counter(STOPBIT, true); + data->rx_status = BUS_LOW; + } +} + +static void dali_lpc11u6x_process_priority_event(struct dali_lpc11u6x_data *data) +{ + dali_lpc11u6x_start_tx(data); +} + +static void dali_lpc11u6x_process_query_event(struct dali_lpc11u6x_data *data) +{ + struct dali_frame timeout_event = { + .data = 0, + .event_type = DALI_EVENT_NO_ANSWER, + }; + k_msgq_put(&data->rx_queue, &timeout_event, K_NO_WAIT); +} + +static void dali_lpc11u6x_handle_work_queue(struct k_work *item) +{ + struct dali_lpc11u6x_data *data = CONTAINER_OF(item, struct dali_lpc11u6x_data, rx_work); + + switch (data->rx_event) { + case CAPTURE: + dali_lpc11u6x_process_capture_event(data); + break; + case STOPBIT: + dali_lpc11u6x_process_stopbit_event(data); + break; + case PRIORITY: + dali_lpc11u6x_process_priority_event(data); + break; + case QUERY: + dali_lpc11u6x_process_query_event(data); + break; + default: + __ASSERT(false, "invalid event type"); + } +} + +static void dali_lpc11u6x_handle_rx_irq(const struct device *dev) +{ + if (LPC_CT32B1->ir & CT32_IR_MR0INT) { + LPC_CT32B1->ir = CT32_IR_MR0INT; + dali_lpc11u6x_enable_rx_counter(STOPBIT, false); + struct dali_lpc11u6x_data *data = dev->data; + data->rx_event = STOPBIT; + k_work_submit_to_queue(&dali_work_queue, &data->rx_work); + } + if (LPC_CT32B1->ir & CT32_IR_MR1INT) { + LPC_CT32B1->ir = CT32_IR_MR1INT; + dali_lpc11u6x_enable_rx_counter(PRIORITY, false); + struct dali_lpc11u6x_data *data = dev->data; + data->rx_event = PRIORITY; + k_work_submit_to_queue(&dali_work_queue, &data->rx_work); + } + if (LPC_CT32B1->ir & CT32_IR_MR2INT) { + LPC_CT32B1->ir = CT32_IR_MR2INT; + dali_lpc11u6x_enable_rx_counter(QUERY, false); + struct dali_lpc11u6x_data *data = dev->data; + data->rx_event = QUERY; + k_work_submit_to_queue(&dali_work_queue, &data->rx_work); + } + if (LPC_CT32B1->ir & CT32_IR_CR0INT) { + LPC_CT32B1->ir = CT32_IR_CR0INT; + struct dali_lpc11u6x_data *data = dev->data; + data->rx_event = CAPTURE; + data->tx_count_on_capture = dali_lpc11u6x_get_tx_counter(); + k_work_submit_to_queue(&dali_work_queue, &data->rx_work); + } +} + +static int dali_lpc11u6x_receive(const struct device *dev, struct dali_frame *frame, + k_timeout_t timeout) +{ + struct dali_lpc11u6x_data *data = dev->data; + __ASSERT(data->rx_queue, "invalid queue"); + + if (k_msgq_get(&data->rx_queue, frame, timeout) < 0) { + return -ENOMSG; + } + return 0; +}; + +static int dali_lpc11u6x_send(const struct device *dev, const struct dali_tx_frame *tx_frame) +{ + const enum dali_event_type frame_type = tx_frame->frame.event_type; + if (frame_type == DALI_EVENT_NONE) { + return 0; + } + + struct dali_lpc11u6x_data *data = dev->data; + if (data->active) { + if (data->active->index_next) { + LOG_ERR("send is busy sending"); + return -EBUSY; + } + } + + switch (frame_type) { + case DALI_FRAME_BACKWARD: + case DALI_FRAME_CORRUPT: + if (tx_frame->is_query) { + return -EINVAL; + } + if (dali_lpc11u6x_is_tx_slot_empty(&data->backward)) { + data->rx_status = TRANSMIT_BACKFRAME; + dali_lpc11u6x_enable_rx_counter(STOPBIT, false); + dali_lpc11u6x_calculate_counts(dev, &data->backward, tx_frame->frame); + data->backward.inter_frame_idle = settling_time_us[0]; + } else { + LOG_ERR("backward frame slot is busy"); + return -EBUSY; + } + break; + case DALI_FRAME_DEVICE: + case DALI_FRAME_GEAR: + case DALI_FRAME_FIRMWARE: + if (dali_lpc11u6x_is_tx_slot_empty(&data->forward)) { + dali_lpc11u6x_calculate_counts(dev, &data->forward, tx_frame->frame); + if (tx_frame->priority < DALI_PRIORITY_1 || + tx_frame->priority > DALI_PRIORITY_5) { + return -EINVAL; + } + data->forward.inter_frame_idle = settling_time_us[tx_frame->priority]; + data->forward.is_query = tx_frame->is_query; + } else { + LOG_ERR("forward frame slot is busy"); + return -EBUSY; + } + break; + default: + return -EINVAL; + } + dali_lpc11u6x_schedule_tx(data); + + return 0; +} + +static void dali_lpc11u6x_abort(const struct device *dev) +{ + dali_lpc11u6x_stop_tx_counter(); + dali_lpc11u6x_set_tx_counter(DALI_TX_ACTIVE); + + struct dali_lpc11u6x_data *data = dev->data; + dali_lpc11u6x_reset_tx_all_slots(data); +} + +static int dali_lpc11u6x_init(const struct device *dev) +{ + struct dali_lpc11u6x_data *data = dev->data; + const struct dali_lpc11u6x_config *config = dev->config; + + /* connect to config */ + data->config = config; + + /* configure interrupts */ + if (config->irq_config_function != NULL) { + config->irq_config_function(); + } + + /* initialize transmission slots */ + dali_lpc11u6x_reset_tx_all_slots(data); + + /* set up receive work queue */ + const struct k_work_queue_config cfg = { + .name = "DALI work", + .no_yield = true, + .essential = false, + }; + k_work_queue_start(&dali_work_queue, dali_work_queue_stack, + K_KERNEL_STACK_SIZEOF(dali_work_queue_stack), + CONFIG_DALI_LPC11U6X_PRIORITY, &cfg); + k_work_init(&data->rx_work, dali_lpc11u6x_handle_work_queue); + + /* initialize receive queue */ + k_msgq_init(&data->rx_queue, data->rx_buffer, sizeof(struct dali_frame), + CONFIG_DALI_MAX_FRAMES_IN_QUEUE); + + /* initialize peripherals */ + dali_lpc11u6x_init_peripheral_clock(); + dali_lpc11u6x_init_io_pins(); + dali_lpc11u6x_set_tx_counter(DALI_TX_IDLE); + dali_lpc11u6x_init_rx_counter(); + + /* examine inital bus state */ + if (dali_lpc11u6x_get_rx_pin()) { + data->rx_status = IDLE; + } else { + data->rx_status = BUS_LOW; + dali_lpc11u6x_set_rx_counter(STOPBIT, DALI_FAILURE_CONDITION_US); + dali_lpc11u6x_enable_rx_counter(STOPBIT, true); + } + return 0; +} + +static DEVICE_API(dali, dali_lpc11u6x_driver_api) = { + .receive = dali_lpc11u6x_receive, + .send = dali_lpc11u6x_send, + .abort = dali_lpc11u6x_abort, +}; + +#define DALI_LPC11U6X_INIT(idx) \ + static void dali_lpc11u6x_config_irq_##idx(void) \ + { \ + IRQ_CONNECT(DT_IRQ_BY_IDX(DT_DRV_INST(idx), 0, irq), \ + DT_IRQ_BY_IDX(DT_DRV_INST(idx), 0, priority), \ + dali_lpc11u6x_handle_tx_irq, DEVICE_DT_INST_GET(idx), 0); \ + irq_enable(DT_IRQ_BY_IDX(DT_DRV_INST(idx), 0, irq)); \ + IRQ_CONNECT(DT_IRQ_BY_IDX(DT_DRV_INST(idx), 1, irq), \ + DT_IRQ_BY_IDX(DT_DRV_INST(idx), 1, priority), \ + dali_lpc11u6x_handle_rx_irq, DEVICE_DT_INST_GET(idx), 0); \ + irq_enable(DT_IRQ_BY_IDX(DT_DRV_INST(idx), 1, irq)); \ + }; \ + static struct dali_lpc11u6x_data dali_lpc11u6x_data_##idx = {0}; \ + static const struct dali_lpc11u6x_config dali_lpc11u6x_config_##idx = { \ + .tx_rise_fall_delta_us = DT_INST_PROP_OR(idx, tx_rise_fall_delta_us, 0), \ + .rx_rise_fall_delta_us = DT_INST_PROP_OR(idx, rx_rise_fall_delta_us, 0), \ + .tx_rx_propagation_min_us = DT_INST_PROP_OR(idx, tx_rx_propagation_min_us, 1), \ + .tx_rx_propagation_max_us = DT_INST_PROP_OR(idx, tx_rx_propagation_max_us, 100), \ + .irq_config_function = dali_lpc11u6x_config_irq_##idx, \ + }; \ + DEVICE_DT_INST_DEFINE(idx, dali_lpc11u6x_init, NULL, &dali_lpc11u6x_data_##idx, \ + &dali_lpc11u6x_config_##idx, POST_KERNEL, \ + CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &dali_lpc11u6x_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(DALI_LPC11U6X_INIT); diff --git a/drivers/dali/dali_pwm.c b/drivers/dali/dali_pwm.c new file mode 100644 index 000000000000..1cabe12d7de6 --- /dev/null +++ b/drivers/dali/dali_pwm.c @@ -0,0 +1,974 @@ +/* + * Copyright (c) 2025 by Sven Hädrich + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT zephyr_dali_pwm + +#include /* api */ + +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(dali_low_level, CONFIG_DALI_LOW_LEVEL_LOG_LEVEL); + +#include "timings.h" /* timing constants from DALI standard */ + +#define DALI_RX_GREY_AREA (20U) +#define SETTLING_TIME_BACKWARD_FRAME_MAX k_us_to_cyc_floor32(DALI_TX_BACKWARD_INTERFRAME_MAX_US) +#define MAX_HALFBIT_TIMES_PER_BACKWARD_FRAME 18 +#define DALI_PWM_NO_RESPONSE_RECEIVED \ + SETTLING_TIME_BACKWARD_FRAME_MAX + \ + k_us_to_cyc_floor32(DALI_TX_HALF_BIT_US) * MAX_HALFBIT_TIMES_PER_BACKWARD_FRAME + +const uint32_t settling_times_min[] = { + k_us_to_cyc_floor32(DALI_TX_BACKWARD_INTERFRAME_MIN_US), /* Settling time backward min */ + k_us_to_cyc_floor32(DALI_TX_PRIO_1_INTERFRAME_MIN_US), /* Settling time prio 1 min */ + k_us_to_cyc_floor32(DALI_TX_PRIO_2_INTERFRAME_MIN_US), /* Settling time prio 2 min */ + k_us_to_cyc_floor32(DALI_TX_PRIO_3_INTERFRAME_MIN_US), /* Settling time prio 3 min */ + k_us_to_cyc_floor32(DALI_TX_PRIO_4_INTERFRAME_MIN_US), /* Settling time prio 4 min */ + k_us_to_cyc_floor32(DALI_TX_PRIO_5_INTERFRAME_MIN_US), /* Settling time prio 5 min */ +}; + +const uint32_t settling_times_length[] = { + k_us_to_cyc_floor32( + DALI_TX_BACKWARD_INTERFRAME_MAX_US - + DALI_TX_BACKWARD_INTERFRAME_MIN_US), /* Settling time backward max - min */ + k_us_to_cyc_floor32(DALI_TX_PRIO_1_INTERFRAME_MAX_US - + DALI_TX_PRIO_1_INTERFRAME_MIN_US), /* Settling time prio 1 max - min */ + k_us_to_cyc_floor32(DALI_TX_PRIO_2_INTERFRAME_MAX_US - + DALI_TX_PRIO_2_INTERFRAME_MIN_US), /* Settling time prio 2 max - min */ + k_us_to_cyc_floor32(DALI_TX_PRIO_3_INTERFRAME_MAX_US - + DALI_TX_PRIO_3_INTERFRAME_MIN_US), /* Settling time prio 3 max - min */ + k_us_to_cyc_floor32(DALI_TX_PRIO_4_INTERFRAME_MAX_US - + DALI_TX_PRIO_4_INTERFRAME_MIN_US), /* Settling time prio 4 max - min */ + k_us_to_cyc_floor32(DALI_TX_PRIO_5_INTERFRAME_MAX_US - + DALI_TX_PRIO_5_INTERFRAME_MIN_US), /* Settling time prio 5 max - min */ +}; + +/** + * ...__ _ _ _ ___ _ _ _ ___ ___ ___ _ _ __________.... + * DALI |_| |_| |_| |_| |_| |_| |_| |___| |___| |___| |_| |___| |_| + * + * TIMING | | | | | | | | | | | | | | | | | | | | + * VALUE 1 1 1 1 0 0 0 0 1 0 1 0 1 0 0 1 1 STOP + * START | DATA | STOP + */ + +enum pwm_states { + NONE, /* Disable sending. */ + LH, /* 2 half bits long, next same as current */ + LHH, /* 3 half bits long, current 1, next 0, next after 0 */ + LLH, /* 3 half bits long, current 0, next 1, next after 1 */ + LLHH, /* 4 half bits long, 3 bit toggle */ + LLLLH, /* 5 half bits long, invalid sequence for corrupted bw frame */ +}; + +enum pwm_capture_state { + PWM_CAPTURE_STATE_BUS_DOWN, + PWM_CAPTURE_STATE_IDLE, + PWM_CAPTURE_STATE_IN_HALFBIT, + PWM_CAPTURE_STATE_AT_BITEND, + PWM_CAPTURE_STATE_IN_CORRUPT, + PWM_CAPTURE_STATE_ERROR, +}; + +/** bit times converted from ns to cycles of timer. To reduce stress on pwm interrupt. */ +struct pwm_timings_cycles { + uint32_t half_min; + uint32_t half_max; + uint32_t full_min; + uint32_t full_max; + uint32_t stop_time; + int32_t rx_flank_shift; +}; + +enum pwm_timing_length { + HALF_BIT_TIME = 0, + START_TIME, + FULL_BIT_TIME, + ERROR_TIME, +}; + +struct pwm_frame { + /** + * DALI frame will be split into a sequence of PWM settings and put + * into this struct. each PWM setting must be send out one after another + * for this to work without disruptions. + */ + enum pwm_states signals[DALI_MAX_BIT_PER_FRAME + 1]; /* plus startbit */ + unsigned int signal_length; /**< How many entries are in the array */ + unsigned int position; /**< Where we are on sending out the entries */ + enum dali_tx_priority priority; /**< Inter frame timing */ + bool is_query; /**< If this frame is a query */ +}; + +struct pwm_capture_helper { + struct k_spinlock lock; + uint32_t data; + uint8_t length; + enum pwm_capture_state state; + uint32_t last_pulse; /* Needed to detect bus_down events. */ +}; + +struct dali_pwm_data { + /** DALI device */ + const struct device *dev; + /** helper for receive */ + struct k_work_delayable frame_finish_work; + struct pwm_capture_helper capture; + struct pwm_timings_cycles timings; + struct k_msgq frames_queue; + char frames_buffer[CONFIG_DALI_MAX_FRAMES_IN_QUEUE * sizeof(struct dali_frame)]; + struct k_sem tx_pwm_sem; + int tx_shift_cyc; + uint32_t bit_time_half_cyc; + /** forward frames see the settling time from the last edge on the bus. */ + uint32_t last_edge_timestamp; + /** backward frames see the settling time from the last edge of their respective forward + * frame */ + uint32_t last_forward_frame_edge_timestamp; + enum pwm_timing_length latest_low; + enum pwm_timing_length latest_high; + struct dali_frame last_frame; + uint32_t last_frame_timestamp; + struct k_work_delayable dali_pwm_no_response_work; + struct pwm_frame forward_frame; + struct pwm_frame backward_frame; +#if defined(CONFIG_DALI_PWM_OWN_THREAD) + struct k_sem tx_queue_sem; + K_KERNEL_STACK_MEMBER(thread_stack, CONFIG_DALI_PWM_THREAD_STACK_SIZE); + struct k_thread thread; +#else + struct k_work_delayable send_work; +#endif +}; + +struct dali_pwm_config { + uint32_t time; + struct pwm_dt_spec rx_capture_pwm; + struct pwm_dt_spec tx_pwm; + int tx_shift_us; + int rx_shift_us; + unsigned int tx_rx_propagation_max_us; + unsigned int rx_finish_work_delay_us; + unsigned int rx_capture_work_delay_us; +}; + +/** this function gets called from pwm_capture to finish the frame, after idle time on bus. */ +static void dali_pwm_finish_frame(struct dali_pwm_data *data) +{ + /* TODO(anyone) make sure, that the bus is really IDLE and not in ERROR state. */ + + if (!data->capture.length) { + /* guard against a race condition from delayed work and recv interrupt. */ + /* don't alter anything, if there is no data. */ + return; + } + const uint32_t time_now = k_cycle_get_32(); + const uint32_t time_difference_cycle = + time_now - data->last_frame_timestamp - + k_us_to_cyc_floor32(data->capture.length * DALI_TX_FULL_BIT_US); + const struct device *dev = data->dev; + const struct dali_pwm_config *config = dev->config; + const uint32_t max_time_twice_cycle = + k_us_to_cyc_floor32(DALI_RX_TWICE_MAX_US + config->rx_finish_work_delay_us); + bool is_send_twice = true; + if (time_difference_cycle > max_time_twice_cycle) { + LOG_DBG("single frame interframe time %d us", + k_cyc_to_us_floor32(time_difference_cycle)); + is_send_twice = false; + } + + if (data->capture.state == PWM_CAPTURE_STATE_IN_CORRUPT) { + struct dali_frame frame = { + .event_type = DALI_FRAME_CORRUPT, + }; + + k_work_cancel_delayable(&data->dali_pwm_no_response_work); + k_msgq_put(&data->frames_queue, &frame, K_NO_WAIT); + } + if (data->capture.state != PWM_CAPTURE_STATE_ERROR) { + struct dali_frame frame = { + .data = data->capture.data, + }; + switch (data->capture.length) { + case DALI_FRAME_BACKWARD_LENGTH: + frame.event_type = DALI_FRAME_BACKWARD; + k_work_cancel_delayable(&data->dali_pwm_no_response_work); + break; + case DALI_FRAME_GEAR_LENGTH: + data->last_forward_frame_edge_timestamp = data->last_edge_timestamp; + if (data->last_frame.event_type == DALI_FRAME_GEAR && + data->last_frame.data == frame.data && is_send_twice) { + frame.event_type = DALI_FRAME_GEAR_TWICE; + } else { + frame.event_type = DALI_FRAME_GEAR; + } + break; + case DALI_FRAME_DEVICE_LENGTH: + data->last_forward_frame_edge_timestamp = data->last_edge_timestamp; + if (data->last_frame.event_type == DALI_FRAME_DEVICE && + data->last_frame.data == frame.data && is_send_twice) { + frame.event_type = DALI_FRAME_DEVICE_TWICE; + } else { + frame.event_type = DALI_FRAME_DEVICE; + } + break; + case DALI_FRAME_UPDATE_LENGTH: + data->last_forward_frame_edge_timestamp = data->last_edge_timestamp; + if (data->last_frame.event_type == DALI_FRAME_FIRMWARE && + data->last_frame.data == frame.data && is_send_twice) { + frame.event_type = DALI_FRAME_FIRMWARE_TWICE; + } else { + frame.event_type = DALI_FRAME_FIRMWARE; + } + break; + default: + frame.event_type = DALI_EVENT_NONE; + } + LOG_INF("{%08x%c%02x %08x}", 0, ':', data->capture.length, data->capture.data); + k_msgq_put(&data->frames_queue, &frame, K_NO_WAIT); + data->last_frame = frame; + data->last_frame_timestamp = time_now; + } + data->capture.state = PWM_CAPTURE_STATE_IDLE; + data->capture.data = 0; + data->capture.length = 0; +} + +static void dali_pwm_finish_frame_work(struct k_work *_work) +{ + struct k_work_delayable *work = k_work_delayable_from_work(_work); + struct dali_pwm_data *data = CONTAINER_OF(work, struct dali_pwm_data, frame_finish_work); + + k_spinlock_key_t capture_key = k_spin_lock(&data->capture.lock); + dali_pwm_finish_frame(data); + k_spin_unlock(&data->capture.lock, capture_key); +} + +static void dali_pwm_no_response_work(struct k_work *_work) +{ + struct k_work_delayable *work = k_work_delayable_from_work(_work); + struct dali_pwm_data *data = + CONTAINER_OF(work, struct dali_pwm_data, dali_pwm_no_response_work); + + struct dali_frame frame = { + .event_type = DALI_EVENT_NO_ANSWER, + }; + k_msgq_put(&data->frames_queue, &frame, K_NO_WAIT); +} + +static enum pwm_timing_length dali_pwm_time_to_length(struct pwm_timings_cycles *timings, + uint32_t time, bool level) +{ + /* this might fail after a timer wrap-around */ + if ((time > timings->stop_time) && level) { + return START_TIME; + } + if (timings->full_min < time && time < timings->full_max) { + return FULL_BIT_TIME; + } + if (timings->half_min < time && time < timings->half_max) { + return HALF_BIT_TIME; + } + return ERROR_TIME; +} + +/* + * This function assumes, that the access to data->capture is locked. + */ +static void dali_pwm_continuous_capture_callback_locked(uint32_t period_cycles, + uint32_t pulse_cycles, int status, + struct dali_pwm_data *data) +{ + if (status == -ERANGE) { + /* timer overflow. Nothing to worry about */ + if (pulse_cycles != data->capture.last_pulse) { + LOG_ERR("Bus power is lost!"); + data->capture.state = PWM_CAPTURE_STATE_BUS_DOWN; + } + data->capture.last_pulse = pulse_cycles; + return; + } + data->capture.last_pulse = pulse_cycles; + + if (data->capture.state == PWM_CAPTURE_STATE_BUS_DOWN) { + LOG_INF("Power back up again!"); + data->capture.state = PWM_CAPTURE_STATE_IDLE; + return; + } + + /* trigger PWM send */ + k_sem_give(&data->tx_pwm_sem); + /* wait for stop condition */ + const struct device *dev = data->dev; + const struct dali_pwm_config *config = dev->config; + const uint32_t stopbit_timeout_us = DALI_RX_BIT_TIME_STOP_US - config->rx_capture_work_delay_us; + k_work_reschedule(&data->frame_finish_work, Z_TIMEOUT_US(stopbit_timeout_us)); + + if (data->timings.rx_flank_shift < 0) { + if (-data->timings.rx_flank_shift >= pulse_cycles) { + data->capture.state = PWM_CAPTURE_STATE_ERROR; + return; + } + } else { + if (pulse_cycles + data->timings.rx_flank_shift >= period_cycles) { + data->capture.state = PWM_CAPTURE_STATE_ERROR; + return; + } + } + pulse_cycles += data->timings.rx_flank_shift; + + enum pwm_timing_length high = dali_pwm_time_to_length(&data->timings, pulse_cycles, true); + enum pwm_timing_length low = + dali_pwm_time_to_length(&data->timings, period_cycles - pulse_cycles, false); + + data->latest_high = high; + data->latest_low = low; + + if (high == START_TIME || data->capture.state == PWM_CAPTURE_STATE_IDLE) { + if (data->capture.length) { + LOG_INF("Frame finish was not called!"); + dali_pwm_finish_frame(data); + } + if (low != HALF_BIT_TIME) { + LOG_DBG("No valid start condition."); + data->capture.state = PWM_CAPTURE_STATE_ERROR; + return; + } + data->capture.state = PWM_CAPTURE_STATE_IN_HALFBIT; + data->capture.data = 0; + data->capture.length = 0; + return; + } + if (data->capture.state == PWM_CAPTURE_STATE_ERROR || + data->capture.state == PWM_CAPTURE_STATE_IN_CORRUPT) { + return; + } + + if (high == ERROR_TIME || low == ERROR_TIME) { + LOG_DBG("received error condition"); + data->capture.state = PWM_CAPTURE_STATE_ERROR; + return; + } + if (data->capture.state == PWM_CAPTURE_STATE_IN_HALFBIT) { + /* .._| */ + /* | 1 | */ + if (high == HALF_BIT_TIME) { + /* _ */ + /* .._| | */ + /* | 1 | */ + /* we already saved the one for the halfbit we are in */ + if (low == HALF_BIT_TIME) { + /* _ */ + /* .._| |_| */ + /* | 1 | 1 */ + data->capture.data = (data->capture.data << 1) | 1; + data->capture.length++; + } else { + /* must be FULL_BIT_TIME */ + /* _ */ + /* .._| |___| */ + /* | 1 | E */ + data->capture.state = PWM_CAPTURE_STATE_ERROR; + } + } else { + /* must be FULL_BIT_TIME */ + /* ___ */ + /* .._| | */ + /* | 1 | 0 */ + data->capture.data = (data->capture.data << 1); + data->capture.length++; + if (low == HALF_BIT_TIME) { + /* ___ */ + /* .._| |_| */ + /* | 1 | 0 | */ + data->capture.state = PWM_CAPTURE_STATE_AT_BITEND; + } else { + /* must be FULL_BIT_TIME */ + /* ___ */ + /* .._| |___| */ + /* | 1 | 0 | 1 */ + data->capture.data = (data->capture.data << 1) | 1; + data->capture.length++; + } + } + } else if (data->capture.state == PWM_CAPTURE_STATE_AT_BITEND) { + /* .._| */ + /* X | */ + if (high == HALF_BIT_TIME) { + /* _ */ + /* .._| | */ + /* X | 0 */ + data->capture.data = (data->capture.data << 1); + data->capture.length++; + if (low == HALF_BIT_TIME) { + /* _ */ + /* .._| |_| */ + /* X | 0 | */ + /* nothing to do */ + } else { + /* must be FULL_BIT_TIME */ + /* _ */ + /* .._| |___| */ + /* X | 0 | 1 */ + data->capture.data = (data->capture.data << 1) | 1; + data->capture.length++; + data->capture.state = PWM_CAPTURE_STATE_IN_HALFBIT; + } + } else { + /* must be FULL_BIT_TIME */ + /* ___ */ + /* .._| | */ + /* X | E */ + data->capture.state = PWM_CAPTURE_STATE_ERROR; + } + } + if (data->capture.length > DALI_MAX_BIT_PER_FRAME) { + data->capture.state = PWM_CAPTURE_STATE_ERROR; + } +} + +static void dali_pwm_continuous_capture_callback(const struct device *dev, uint32_t pwm, + uint32_t period_cycles, uint32_t pulse_cycles, + int status, void *user_data) +{ + struct dali_pwm_data *data = user_data; + /* It may not be 100% accurate, but good enough. */ + uint32_t timestamp = k_cycle_get_32(); + k_spinlock_key_t capture_key = k_spin_lock(&data->capture.lock); + dali_pwm_continuous_capture_callback_locked(period_cycles, pulse_cycles, status, data); + k_spin_unlock(&data->capture.lock, capture_key); + data->last_edge_timestamp = timestamp; +} + +static inline int dali_pwm_set_cycles(struct dali_pwm_data *data, const struct pwm_dt_spec *spec, + enum pwm_states state) +{ + uint32_t period = 0, pulse = 0; + + switch (state) { + case NONE: + period = 0; + pulse = 0; + break; + case LH: + period = data->bit_time_half_cyc * 2; + pulse = data->bit_time_half_cyc + data->tx_shift_cyc; + break; + case LHH: + period = data->bit_time_half_cyc * 3; + pulse = data->bit_time_half_cyc + data->tx_shift_cyc; + break; + case LLH: + period = data->bit_time_half_cyc * 3; + pulse = data->bit_time_half_cyc * 2 + data->tx_shift_cyc; + break; + case LLHH: + period = data->bit_time_half_cyc * 4; + pulse = data->bit_time_half_cyc * 2 + data->tx_shift_cyc; + break; + case LLLLH: + period = data->bit_time_half_cyc * 5; + pulse = data->bit_time_half_cyc * 4 + data->tx_shift_cyc; + break; + } + + return pwm_set_cycles(spec->dev, spec->channel, period, pulse, spec->flags); +} + +static void dali_pwm_generate_corrupt_frame(struct pwm_frame *pwm) +{ + pwm->signal_length = 0; + pwm->position = 0; + pwm->priority = DALI_PRIORITY_BACKWARD_FRAME; + + /* Send all ones, except for the second, where we stretch the active state over + the corrupt threshold */ + for (int i = 0; i < DALI_FRAME_BACKWARD_LENGTH + 1; i++) { + pwm->signals[pwm->signal_length++] = i == 2 ? LLLLH : LH; + } +} + +/** + * @brief construct PWM patterns for dali frame and return them. + * + */ +static int dali_pwm_generate_frame(const struct dali_frame *frame, enum dali_tx_priority priority, + bool is_query, struct pwm_frame *pwm) +{ + int length = 0; + switch (frame->event_type) { + case DALI_FRAME_CORRUPT: + dali_pwm_generate_corrupt_frame(pwm); + return 0; + case DALI_FRAME_BACKWARD: + length = DALI_FRAME_BACKWARD_LENGTH; + break; + case DALI_FRAME_GEAR: + length = DALI_FRAME_GEAR_LENGTH; + break; + case DALI_FRAME_DEVICE: + length = DALI_FRAME_DEVICE_LENGTH; + break; + case DALI_FRAME_FIRMWARE: + length = DALI_FRAME_UPDATE_LENGTH; + break; + default: + return -EINVAL; + } + + memset(pwm, 0, sizeof(struct pwm_frame)); + + pwm->priority = priority; + pwm->is_query = is_query; + + /* We iterate over the frame in full and half bits. */ + int shift_half_bit = 0; + /* Startbit is 1 and is added here. */ + bool current_bit = true; + bool next_bit = !!(frame->data & (1 << (length - 1))); + bool next_next_bit = !!(frame->data & (1 << (length - 2))); + LOG_DBG("Generating new frame with data %08x and length %d", frame->data, length); + while (length > 0) { + if (current_bit == next_bit) { + pwm->signals[pwm->signal_length] = LH; + shift_half_bit += 2; + } else if (current_bit == next_next_bit && shift_half_bit == 1) { + pwm->signals[pwm->signal_length] = LLHH; + shift_half_bit += 4; + } else if (current_bit) { + pwm->signals[pwm->signal_length] = LHH; + shift_half_bit += 3; + } else { + pwm->signals[pwm->signal_length] = LLH; + shift_half_bit += 3; + } + pwm->signal_length++; + while (shift_half_bit > 1) { + length--; + current_bit = next_bit; + next_bit = next_next_bit; + if (length > 1) { + next_next_bit = !!(frame->data & (1 << (length - 2))); + } + /* no need for an else branch, we want the next_next_bit to be the same as + */ + /* the next_bit, which is already the case. */ + shift_half_bit -= 2; + } + } + + /* Check if there is a signal missing at the end */ + if (shift_half_bit || (current_bit && next_bit && length == 0)) { + /* We need to add the signal for the last bit. + it could either be, that we are in the middle of the 0 bit and need + to send the last half of the zero, or that we are missing a full 1. + Signal could also be LHH, it just needs a short low bit, we disable the + pwm after this and high level is disabled. */ + pwm->signals[pwm->signal_length] = LH; + pwm->signal_length++; + } + + return 0; +} + +static inline bool dali_pwm_is_frame_time_as_expected(enum pwm_timing_length low, + enum pwm_timing_length high, + enum pwm_states latest, + enum pwm_states second_to_latest) +{ + /** + * pwm capture and pwm generate are offset by the period. + * capture high + * capture low + * ______________ __________ __________ + * |_____________________| |__________| |______________- + * | second to latest | latest pwm | + * + * ^ current time-point + */ + + if (low == ERROR_TIME || high == ERROR_TIME) { + return false; + } + if (low == HALF_BIT_TIME && (latest != LH && latest != LHH)) { + return false; + } + if (low == FULL_BIT_TIME && (latest != LLH && latest != LLHH)) { + return false; + } + if (high == HALF_BIT_TIME && + (second_to_latest != LH && second_to_latest != LLH && second_to_latest != LLLLH)) { + return false; + } + if (high == FULL_BIT_TIME && (second_to_latest != LHH && second_to_latest != LLHH)) { + return false; + } + return true; +} + +static k_timeout_t dali_pwm_process_sendout(const struct device *dev) +{ + const struct dali_pwm_config *config = dev->config; + struct dali_pwm_data *data = dev->data; + struct pwm_frame *frame; + int ret; + + if (data->backward_frame.signal_length) { + /* prioritize backward frames */ + frame = &data->backward_frame; + } else if (data->forward_frame.signal_length) { + /* forward frames as fallback */ + frame = &data->forward_frame; + } else { + /* no frame to send, we wait forever until there is an entry */ + return K_FOREVER; + } + + uint32_t time_difference = k_cycle_get_32() - data->last_edge_timestamp; + if (time_difference < settling_times_min[frame->priority]) { + /* should be somehow random */ + uint32_t random = + settling_times_length[frame->priority] * (time_difference & 3) / 4; + + uint32_t sleep_time = + settling_times_min[frame->priority] - time_difference + random; + + return Z_TIMEOUT_CYC(sleep_time); + } + + /* time difference is bigger then the minimal wait time, so we send out the frame */ + LOG_DBG("Sending frame with prio %d", frame->priority); + + /* start sending */ + while (true) { + if (frame->position != 0 && data->capture.state == PWM_CAPTURE_STATE_ERROR) { + /* We had an error decoding frames we send.... this must be a collision. */ + /* TODO(anyone) maybe send the break condition and retry sending the frame + */ + ret = pwm_set_pulse_dt(&config->tx_pwm, 0); + LOG_DBG("Capture Error"); + break; + } + + if (frame->position == 1) { + if (data->latest_low != HALF_BIT_TIME || data->latest_high != START_TIME) { + LOG_DBG("We are not receiving what we are sending."); + pwm_set_pulse_dt(&config->tx_pwm, 0); + break; + } + } else if (frame->position > 1) { + if (!dali_pwm_is_frame_time_as_expected( + data->latest_low, data->latest_high, + frame->signals[frame->position - 1], + frame->signals[frame->position - 2])) { + LOG_DBG("We are not receiving what we are sending."); + pwm_set_pulse_dt(&config->tx_pwm, 0); + break; + } + } + /* Check if everything has been send. -> disable PWM */ + if (frame->position >= frame->signal_length) { + /* send everything, stop now. */ + ret = dali_pwm_set_cycles(data, &config->tx_pwm, NONE); + LOG_DBG("frame is sent."); + break; + } + + /* check if the pattern has changed, if not, we don't need to do anything and can + just skip the reconfigure. */ + if (frame->position == 0 || + frame->signals[frame->position] != frame->signals[frame->position - 1]) { + ret = dali_pwm_set_cycles(data, &config->tx_pwm, + frame->signals[frame->position]); + if (ret < 0) { + LOG_DBG("failed to set pwm: %d", ret); + break; + } + if (frame->position == 0) { + /* "reset" semaphore to 0, otherwise, the loop will run twice + before the first PWM setting is even in effect. */ + k_sem_take(&data->tx_pwm_sem, K_NO_WAIT); + /* the semaphore is given every time the bus returns from low to + high, the bus on the other hand can be pulled low by either us, + or any other device. Since we don't know when the pwm will take + effect, we cannot know when we are starting to send and need to + reset this semaphore. This point seemed close enough with the + scheduler, that it should hopefully never happen, that it gets + falsely given. */ + } + } + frame->position++; + + /* wait for the flank */ + if (k_sem_take(&data->tx_pwm_sem, Z_TIMEOUT_US(config->tx_rx_propagation_max_us)) < + 0) { + /* Timeout while waiting for semaphore. + There is a bus-error going on and we need to stop sending. + this is only a method of last resort and should theoretically + never happen as we have an idle state on enter and the TX must + result in a flank. */ + dali_pwm_set_cycles(data, &config->tx_pwm, NONE); + LOG_DBG("BUS error at position %d", frame->position); + break; + } + } + k_spinlock_key_t capture_key = k_spin_lock(&data->capture.lock); + k_spin_unlock(&data->capture.lock, capture_key); + if (frame->is_query) { + k_work_reschedule(&data->dali_pwm_no_response_work, + Z_TIMEOUT_CYC(DALI_PWM_NO_RESPONSE_RECEIVED)); + } + /* mark frame as send and not valid anymore. */ + frame->signal_length = 0; + return K_NO_WAIT; +} + +#if defined(CONFIG_DALI_PWM_OWN_THREAD) +static void dali_pwm_tx_thread(void *arg1, void *unused1, void *unused2) +{ + k_thread_name_set(NULL, "dali_pwm_tx_thread"); + + ARG_UNUSED(unused1); + ARG_UNUSED(unused2); + + const struct device *dev = (const struct device *)arg1; + struct dali_pwm_data *data = dev->data; + + while (true) { + k_timeout_t timeout = dali_pwm_process_sendout(dev); + /* either sleep until we are ready to send the first entry, or until + * there is a new entry in the list, which might be a new first entry. + */ + k_sem_take(&data->tx_queue_sem, timeout); + } +} +#else +static void dali_pwm_tx_work_cb(struct k_work *_work) +{ + struct k_work_delayable *work = k_work_delayable_from_work(_work); + struct dali_pwm_data *data = CONTAINER_OF(work, struct dali_pwm_data, send_work); + + k_timeout_t timeout = dali_pwm_process_sendout(data->dev); + if (!K_TIMEOUT_EQ(timeout, K_FOREVER)) { + k_work_reschedule(work, timeout); + } +} +#endif + +static int dali_pwm_us_to_cycles(int us, uint64_t cycles_per_sec) +{ + return ((int64_t)cycles_per_sec * (int64_t)us) / USEC_PER_SEC; +} + +static int dali_pwm_receive(const struct device *dev, struct dali_frame *frame, k_timeout_t timeout) +{ + struct dali_pwm_data *data = dev->data; + __ASSERT_NO_MSG(&data->frames_queue); + + if (k_msgq_get(&data->frames_queue, frame, timeout) < 0) { + return -ENOMSG; + } + return 0; +} + +static int dali_pwm_send(const struct device *dev, const struct dali_tx_frame *tx_frame) +{ + struct dali_pwm_data *data = dev->data; + int ret = 0; + struct pwm_frame *working_frame; + enum dali_tx_priority priority; + bool is_query; + + if (tx_frame->frame.event_type == DALI_EVENT_NONE) { + return ret; + } + + if (tx_frame->frame.event_type == DALI_FRAME_CORRUPT || + tx_frame->frame.event_type == DALI_FRAME_BACKWARD) { + /* We are currently sending, this cannot be a response anymore */ + if ((data->forward_frame.signal_length && data->forward_frame.position) || + (data->backward_frame.signal_length && data->backward_frame.position)) { + return -ETIMEDOUT; + } + /* it is to late the send the response */ + if (k_cycle_get_32() - data->last_forward_frame_edge_timestamp > + SETTLING_TIME_BACKWARD_FRAME_MAX) { + return -ETIMEDOUT; + } + working_frame = &data->backward_frame; + priority = DALI_PRIORITY_BACKWARD_FRAME; + is_query = false; + } else { + working_frame = &data->forward_frame; + if (tx_frame->priority < DALI_PRIORITY_1 || tx_frame->priority > DALI_PRIORITY_5) { + return -EINVAL; + } + priority = tx_frame->priority; + is_query = tx_frame->is_query; + } + + /* Check if we can store the frame. */ + if (working_frame->signal_length) { + return -EBUSY; + } + + ret = dali_pwm_generate_frame(&(tx_frame->frame), priority, is_query, working_frame); + if (ret < 0) { + return ret; + } + + /* trigger sending out */ +#if defined(CONFIG_DALI_PWM_OWN_THREAD) + k_sem_give(&data->tx_queue_sem); +#else + k_work_reschedule(&data->send_work, K_NO_WAIT); +#endif + + return 0; +} + +static void dali_pwm_abort(const struct device *dev) +{ + struct dali_pwm_data *data = dev->data; + + /* set signal length to 0 will abort sending and remove frame. No need to delete data */ + data->forward_frame.signal_length = 0; + /* we don't abort backward frames, as they are not resend. */ +} + +static int dali_pwm_init(const struct device *dev) +{ + const struct dali_pwm_config *config = dev->config; + struct dali_pwm_data *data = dev->data; + int ret; + + data->dev = dev; + + LOG_DBG("PWM INIT"); + + k_msgq_init(&data->frames_queue, data->frames_buffer, sizeof(struct dali_frame), + CONFIG_DALI_MAX_FRAMES_IN_QUEUE); + +#if defined(CONFIG_DALI_PWM_OWN_THREAD) + ret = k_sem_init(&data->tx_queue_sem, 0, 1); + if (ret < 0) { + LOG_ERR("Could not initialize send messagequeue semaphore."); + return ret; + } + + k_thread_create(&data->thread, data->thread_stack, CONFIG_DALI_PWM_THREAD_STACK_SIZE, + dali_pwm_tx_thread, (void *)dev, NULL, NULL, + K_PRIO_COOP(CONFIG_DALI_PWM_THREAD_PRIORITY), 0, K_NO_WAIT); +#else + k_work_init_delayable(&data->send_work, dali_pwm_tx_work_cb); +#endif + + ret = k_sem_init(&data->tx_pwm_sem, 0, 1); + if (ret < 0) { + LOG_ERR("Could not initialize PWM semaphore."); + return ret; + } + + if (!pwm_is_ready_dt(&config->tx_pwm)) { + LOG_ERR("PWM device %s is not ready", config->tx_pwm.dev->name); + return -ENODEV; + } + + if (!pwm_is_ready_dt(&config->rx_capture_pwm)) { + LOG_ERR("PWM device %s is not ready", config->rx_capture_pwm.dev->name); + return -ENODEV; + } + + uint64_t cycles_per_sec; + ret = pwm_get_cycles_per_sec(config->tx_pwm.dev, config->tx_pwm.channel, &cycles_per_sec); + if (ret) { + LOG_ERR("Could not get cycles per sec for tx channel."); + return ret; + } + if (cycles_per_sec < 200000) { + LOG_ERR("PWM timer is not accurate enough. Need at least 200kHz. Have %lldHz", + cycles_per_sec); + return -ERANGE; + } + data->bit_time_half_cyc = dali_pwm_us_to_cycles(DALI_TX_HALF_BIT_US, cycles_per_sec); + data->tx_shift_cyc = dali_pwm_us_to_cycles(config->tx_shift_us, cycles_per_sec); + + pwm_get_cycles_per_sec(config->rx_capture_pwm.dev, config->rx_capture_pwm.channel, + &cycles_per_sec); + /* dali bit timings should be accurate to about 5 us => 200khz */ + if (cycles_per_sec < 200000) { + LOG_ERR("Capture timer is not accurate enough. Need at least 200kHz. Have %lldHz", + cycles_per_sec); + return -ERANGE; + } + data->timings.half_min = dali_pwm_us_to_cycles( + (DALI_RX_BIT_TIME_HALF_MIN_US - DALI_RX_GREY_AREA), cycles_per_sec); + data->timings.half_max = dali_pwm_us_to_cycles( + (DALI_RX_BIT_TIME_HALF_MAX_US + DALI_RX_GREY_AREA), cycles_per_sec); + data->timings.full_min = dali_pwm_us_to_cycles( + (DALI_RX_BIT_TIME_FULL_MIN_US - DALI_RX_GREY_AREA), cycles_per_sec); + data->timings.full_max = dali_pwm_us_to_cycles( + (DALI_RX_BIT_TIME_FULL_MAX_US + DALI_RX_GREY_AREA), cycles_per_sec); + data->timings.stop_time = + dali_pwm_us_to_cycles((DALI_RX_BIT_TIME_STOP_US - 50U), cycles_per_sec); + data->timings.rx_flank_shift = dali_pwm_us_to_cycles(config->rx_shift_us, cycles_per_sec); + + ret = pwm_configure_capture(config->rx_capture_pwm.dev, config->rx_capture_pwm.channel, + PWM_CAPTURE_MODE_CONTINUOUS | PWM_CAPTURE_TYPE_BOTH | + config->rx_capture_pwm.flags, + dali_pwm_continuous_capture_callback, data); + if (ret < 0) { + LOG_ERR("Could not configure capture. %s", strerror(-ret)); + return ret; + } + + ret = pwm_enable_capture(config->rx_capture_pwm.dev, config->rx_capture_pwm.channel); + if (ret < 0) { + LOG_ERR("Could not configure capture. %s", strerror(-ret)); + return ret; + } + + return 0; +} + +static DEVICE_API(dali, dali_pwm_driver_api) = { + .receive = dali_pwm_receive, + .send = dali_pwm_send, + .abort = dali_pwm_abort, +}; + +#define DALI_PWM_INIT(idx) \ + BUILD_ASSERT(DT_INST_PROP_OR(idx, tx_flank_shift_us, 0) < DALI_TX_HALF_BIT_US || \ + (DT_INST_PROP_OR(idx, tx_flank_shift_us, 0) ^ 0xffffffff) < \ + DALI_TX_HALF_BIT_US, \ + "Edge-shift must be lower than 416us."); \ + static struct dali_pwm_data dali_pwm_data_##idx = { \ + .backward_frame.signal_length = 0, \ + .forward_frame.signal_length = 0, \ + .capture.state = PWM_CAPTURE_STATE_IDLE, \ + .capture.data = 0, \ + .capture.length = 0, \ + .frame_finish_work = Z_WORK_DELAYABLE_INITIALIZER(dali_pwm_finish_frame_work), \ + .dali_pwm_no_response_work = \ + Z_WORK_DELAYABLE_INITIALIZER(dali_pwm_no_response_work), \ + }; \ + static const struct dali_pwm_config dali_pwm_config_##idx = { \ + .rx_capture_pwm = PWM_DT_SPEC_INST_GET_BY_IDX(idx, 1), \ + .tx_pwm = PWM_DT_SPEC_INST_GET_BY_IDX(idx, 0), \ + .tx_shift_us = DT_INST_PROP_OR(idx, tx_flank_shift_us, 0), \ + .rx_shift_us = DT_INST_PROP_OR(idx, rx_flank_shift_us, 0), \ + .tx_rx_propagation_max_us = DT_INST_PROP_OR(idx, tx_rx_propagation_max_us, 200), \ + .rx_finish_work_delay_us = DT_INST_PROP_OR(idx, rx_finish_work_delay_us, 800), \ + .rx_capture_work_delay_us = DT_INST_PROP_OR(idx, rx_capture_work_delay_us, 200), \ + }; \ + DEVICE_DT_INST_DEFINE(idx, dali_pwm_init, NULL, &dali_pwm_data_##idx, \ + &dali_pwm_config_##idx, POST_KERNEL, \ + CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &dali_pwm_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(DALI_PWM_INIT); diff --git a/drivers/dali/lpc11u6x.h b/drivers/dali/lpc11u6x.h new file mode 100644 index 000000000000..6f50cb55b9e2 --- /dev/null +++ b/drivers/dali/lpc11u6x.h @@ -0,0 +1,477 @@ +/* + * Copyright (c) 2025 by Sven Hädrich + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_DALI_LPC11U6X_H_ +#define ZEPHYR_DRIVERS_DALI_LPC11U6X_H_ + +/* + * register bit pattern definitions for the LPC11U6x platform. + * + * see UM10732 - LPC11U6x User Manual for reference + */ + +/* peripheral memory map */ +#define LPC_I2C0_BASE 0x40000000UL +#define LPC_WWDT_BASE 0x40004000UL +#define LPC_USART0_BASE 0x40008000UL +#define LPC_CT16B0_BASE 0x4000C000UL +#define LPC_CT16B1_BASE 0x40010000UL +#define LPC_CT32B0_BASE 0x40014000UL +#define LPC_CT32B1_BASE 0x40018000UL +#define LPC_ADC_BASE 0x4001C000UL +#define LPC_I2C1_BASE 0x40020000UL +#define LPC_RTC_BASE 0x40024000UL +#define LPC_DMATRIGMUX_BASE 0x40028000UL +#define LPC_PMU_BASE 0x40038000UL +#define LPC_FLASHCTRL_BASE 0x4003C000UL +#define LPC_SSP0_BASE 0x40040000UL +#define LPC_IOCON_BASE 0x40044000UL +#define LPC_SYSCON_BASE 0x40048000UL +#define LPC_USART4_BASE 0x4004C000UL +#define LPC_SSP1_BASE 0x40058000UL +#define LPC_GINT0_BASE 0x4005C000UL +#define LPC_GINT1_BASE 0x40060000UL +#define LPC_USART1_BASE 0x4006C000UL +#define LPC_USART2_BASE 0x40070000UL +#define LPC_USART3_BASE 0x40074000UL +#define LPC_USB_BASE 0x40080000UL +#define LPC_CRC_BASE 0x50000000UL +#define LPC_DMA_BASE 0x50004000UL +#define LPC_DMA_CH_BASE 0x50004400UL +#define LPC_SCT0_BASE 0x5000C000UL +#define LPC_SCT1_BASE 0x5000E000UL +#define LPC_GPIO_PORT_BASE 0xA0000000UL +#define LPC_PINT_BASE 0xA0004000UL + +/* 4.4 Register description*/ +struct lpc_syscon { /*!< (@ 0x40048000) SYSCON Structure */ + volatile uint32_t SYSMEMREMAP; /*!< (@ 0x40048000) System memory remap */ + volatile uint32_t PRESETCTRL; /*!< (@ 0x40048004) Peripheral reset control */ + volatile uint32_t SYSPLLCTRL; /*!< (@ 0x40048008) System PLL control */ + volatile const uint32_t SYSPLLSTAT; /*!< (@ 0x4004800C) System PLL status */ + volatile uint32_t USBPLLCTRL; /*!< (@ 0x40048010) USB PLL control */ + volatile const uint32_t USBPLLSTAT; /*!< (@ 0x40048014) USB PLL status */ + volatile const uint32_t RESERVED0; + volatile uint32_t + RTCOSCCTRL; /*!< (@ 0x4004801C) Ultra-low power 32 kHz oscillator output control */ + volatile uint32_t SYSOSCCTRL; /*!< (@ 0x40048020) System oscillator control */ + volatile uint32_t WDTOSCCTRL; /*!< (@ 0x40048024) Watchdog oscillator control */ + volatile const uint32_t RESERVED1[2]; + volatile uint32_t SYSRSTSTAT; /*!< (@ 0x40048030) System reset status register */ + volatile const uint32_t RESERVED2[3]; + volatile uint32_t SYSPLLCLKSEL; /*!< (@ 0x40048040) System PLL clock source select */ + volatile uint32_t SYSPLLCLKUEN; /*!< (@ 0x40048044) System PLL clock source update enable */ + volatile uint32_t USBPLLCLKSEL; /*!< (@ 0x40048048) USB PLL clock source select */ + volatile uint32_t USBPLLCLKUEN; /*!< (@ 0x4004804C) USB PLL clock source update enable */ + volatile const uint32_t RESERVED3[8]; + volatile uint32_t MAINCLKSEL; /*!< (@ 0x40048070) Main clock source select */ + volatile uint32_t MAINCLKUEN; /*!< (@ 0x40048074) Main clock source update enable */ + volatile uint32_t SYSAHBCLKDIV; /*!< (@ 0x40048078) System clock divider */ + volatile const uint32_t RESERVED4; + volatile uint32_t SYSAHBCLKCTRL; /*!< (@ 0x40048080) System clock control */ + volatile const uint32_t RESERVED5[4]; + volatile uint32_t SSP0CLKDIV; /*!< (@ 0x40048094) SSP0 clock divider */ + volatile uint32_t USART0CLKDIV; /*!< (@ 0x40048098) USART0 clock divider */ + volatile uint32_t SSP1CLKDIV; /*!< (@ 0x4004809C) SSP1 clock divider */ + volatile uint32_t FRGCLKDIV; /*!< (@ 0x400480A0) Clock divider for the common fractional + baud rate generator of USART1 to USART4 */ + volatile const uint32_t RESERVED6[7]; + volatile uint32_t USBCLKSEL; /*!< (@ 0x400480C0) USB clock source select */ + volatile uint32_t USBCLKUEN; /*!< (@ 0x400480C4) USB clock source update enable */ + volatile uint32_t USBCLKDIV; /*!< (@ 0x400480C8) USB clock source divider */ + volatile const uint32_t RESERVED7[5]; + volatile uint32_t CLKOUTSEL; /*!< (@ 0x400480E0) CLKOUT clock source select */ + volatile uint32_t CLKOUTUEN; /*!< (@ 0x400480E4) CLKOUT clock source update enable */ + volatile uint32_t CLKOUTDIV; /*!< (@ 0x400480E8) CLKOUT clock divider */ + volatile const uint32_t RESERVED8; + volatile uint32_t + UARTFRGDIV; /*!< (@ 0x400480F0) USART fractional generator divider value */ + volatile uint32_t + UARTFRGMULT; /*!< (@ 0x400480F4) USART fractional generator multiplier value */ + volatile const uint32_t RESERVED9; + volatile uint32_t EXTTRACECMD; /*!< (@ 0x400480FC) External trace buffer command register */ + volatile const uint32_t pioPORCAP0; /*!< (@ 0x40048100) POR captured pio status 0 */ + volatile const uint32_t pioPORCAP1; /*!< (@ 0x40048104) POR captured pio status 1 */ + volatile const uint32_t pioPORCAP2; /*!< (@ 0x40048108) POR captured pio status 1 */ + volatile const uint32_t RESERVED10[10]; + volatile uint32_t IOCONCLKDIV6; /*!< (@ 0x40048134) Peripheral clock 6 to the IOCON block + for programmable glitch filter */ + volatile uint32_t IOCONCLKDIV5; /*!< (@ 0x40048138) Peripheral clock 5 to the IOCON block + for programmable glitch filter */ + volatile uint32_t IOCONCLKDIV4; /*!< (@ 0x4004813C) Peripheral clock 4 to the IOCON block + for programmable glitch filter */ + volatile uint32_t IOCONCLKDIV3; /*!< (@ 0x40048140) Peripheral clock 3 to the IOCON block + for programmable glitch filter */ + volatile uint32_t IOCONCLKDIV2; /*!< (@ 0x40048144) Peripheral clock 2 to the IOCON block + for programmable glitch filter */ + volatile uint32_t IOCONCLKDIV1; /*!< (@ 0x40048148) Peripheral clock 1 to the IOCON block + for programmable glitch filter */ + volatile uint32_t IOCONCLKDIV0; /*!< (@ 0x4004814C) Peripheral clock 0 to the IOCON block + for programmable glitch filter */ + volatile uint32_t BODCTRL; /*!< (@ 0x40048150) Brown-Out Detect */ + volatile uint32_t SYSTCKCAL; /*!< (@ 0x40048154) System tick counter calibration */ + volatile uint32_t AHBMATRIXPRIO; /*!< (@ 0x40048158) AHB matrix priority configuration */ + volatile const uint32_t RESERVED11[5]; + volatile uint32_t IRQLATENCY; /*!< (@ 0x40048170) IRQ delay. Allows trade-off between + interrupt latency and determinism. */ + volatile uint32_t NMISRC; /*!< (@ 0x40048174) NMI Source Control */ + volatile uint32_t PINTSEL[8]; /*!< (@ 0x40048178) Gpio Pin Interrupt Select register 0 */ + volatile uint32_t USBCLKCTRL; /*!< (@ 0x40048198) USB clock control */ + volatile const uint32_t USBCLKST; /*!< (@ 0x4004819C) USB clock status */ + volatile const uint32_t RESERVED12[25]; + volatile uint32_t + STARTERP0; /*!< (@ 0x40048204) Start logic 0 interrupt wake-up enable register 0 */ + volatile const uint32_t RESERVED13[3]; + volatile uint32_t + STARTERP1; /*!< (@ 0x40048214) Start logic 1 interrupt wake-up enable register 1 */ + volatile const uint32_t RESERVED14[6]; + volatile uint32_t PDSLEEPCFG; /*!< (@ 0x40048230) Power-down states in deep-sleep mode */ + volatile uint32_t + PDAWAKECFG; /*!< (@ 0x40048234) Power-down states for wake-up from deep-sleep */ + volatile uint32_t PDRUNCFG; /*!< (@ 0x40048238) Power configuration register */ + volatile const uint32_t RESERVED15[110]; + volatile const uint32_t DEVICE_ID; /*!< (@ 0x400483F4) Device ID */ +}; + +#define LPC_SYSCON ((struct lpc_syscon *)LPC_SYSCON_BASE) +#define SYSAHBCLKCTRL_IOCON (1U << 16U) +#define SYSAHBCLKCTRL_Gpio (1U << 6U) +#define SYSAHBCLKCTRL_CT32B0 (1U << 9U) +#define SYSAHBCLKCTRL_CT32B1 (1U << 10U) +#define SYSAHBCLKCTRL_SCT0_1 (1U << 31U) + +/* 4.4.2 peripheral reset control register */ +#define SSP0_RST_N (1U << 0U) +#define I2C0_RST_N (1U << 1U) +#define SSP1_RST_N (1U << 2U) +#define I2C1_RST_N (1U << 3U) +#define FRG_RST_N (1U << 4U) +#define SCT0_RST_N (1U << 9U) +#define SCT1_RST_N (1U << 10U) + +/* 6. I/O control */ +struct lpc_io_control { /*!< (@ 0x40044000) IOCON Structure */ + volatile uint32_t pio0_0; /*!< (@ 0x40044000) I/O configuration for port pio0 */ + volatile uint32_t pioO0_1; /*!< (@ 0x40044004) I/O configuration for port pio0 */ + volatile uint32_t pio0_2; /*!< (@ 0x40044008) I/O configuration for port pio0 */ + volatile uint32_t pio0_3; /*!< (@ 0x4004400C) I/O configuration for port pio0 */ + volatile uint32_t pio0_4; /*!< (@ 0x40044010) I/O configuration for port pio0 */ + volatile uint32_t pio0_5; /*!< (@ 0x40044014) I/O configuration for port pio0 */ + volatile uint32_t pio0_6; /*!< (@ 0x40044018) I/O configuration for port pio0 */ + volatile uint32_t pio0_7; /*!< (@ 0x4004401C) I/O configuration for port pio0 */ + volatile uint32_t pio0_8; /*!< (@ 0x40044020) I/O configuration for port pio0 */ + volatile uint32_t pio0_9; /*!< (@ 0x40044024) I/O configuration for port pio0 */ + volatile uint32_t pio0_10; /*!< (@ 0x40044028) I/O configuration for port pio0 */ + volatile uint32_t pio0_11; /*!< (@ 0x4004402C) I/O configuration for port pio0 */ + volatile uint32_t pio0_12; /*!< (@ 0x40044030) I/O configuration for port pio0 */ + volatile uint32_t pio0_13; /*!< (@ 0x40044034) I/O configuration for port pio0 */ + volatile uint32_t pio0_14; /*!< (@ 0x40044038) I/O configuration for port pio0 */ + volatile uint32_t pio0_15; /*!< (@ 0x4004403C) I/O configuration for port pio0 */ + volatile uint32_t pio0_16; /*!< (@ 0x40044040) I/O configuration for port pio0 */ + volatile uint32_t pio0_17; /*!< (@ 0x40044044) I/O configuration for port pio0 */ + volatile uint32_t pio0_18; /*!< (@ 0x40044048) I/O configuration for port pio0 */ + volatile uint32_t pio0_19; /*!< (@ 0x4004404C) I/O configuration for port pio0 */ + volatile uint32_t pio0_20; /*!< (@ 0x40044050) I/O configuration for port pio0 */ + volatile uint32_t pio0_21; /*!< (@ 0x40044054) I/O configuration for port pio0 */ + volatile uint32_t pio0_22; /*!< (@ 0x40044058) I/O configuration for port pio0 */ + volatile uint32_t pio0_23; /*!< (@ 0x4004405C) I/O configuration for port pio0 */ + volatile uint32_t pio1_0; /*!< (@ 0x40044060) I/O configuration for port pio1 */ + volatile uint32_t pio1_1; /*!< (@ 0x40044064) I/O configuration for port pio1 */ + volatile uint32_t pio1_2; /*!< (@ 0x40044068) I/O configuration for port pio1 */ + volatile uint32_t pio1_3; /*!< (@ 0x4004406C) I/O configuration for port pio1 */ + volatile uint32_t pio1_4; /*!< (@ 0x40044070) I/O configuration for port pio1 */ + volatile uint32_t pio1_5; /*!< (@ 0x40044074) I/O configuration for port pio1 */ + volatile uint32_t pio1_6; /*!< (@ 0x40044078) I/O configuration for port pio1 */ + volatile uint32_t pio1_7; /*!< (@ 0x4004407C) I/O configuration for port pio1 */ + volatile uint32_t pio1_8; /*!< (@ 0x40044080) I/O configuration for port pio1 */ + volatile uint32_t pio1_9; /*!< (@ 0x40044084) I/O configuration for port pio1 */ + volatile uint32_t pio1_10; /*!< (@ 0x40044088) I/O configuration for port pio1 */ + volatile uint32_t pio1_11; /*!< (@ 0x4004408C) I/O configuration for port pio1 */ + volatile uint32_t pio1_12; /*!< (@ 0x40044090) I/O configuration for port pio1 */ + volatile uint32_t pio1_13; /*!< (@ 0x40044094) I/O configuration for port pio1 */ + volatile uint32_t pio1_14; /*!< (@ 0x40044098) I/O configuration for port pio1 */ + volatile uint32_t pio1_15; /*!< (@ 0x4004409C) I/O configuration for port pio1 */ + volatile uint32_t pio1_16; /*!< (@ 0x400440A0) I/O configuration for port pio1 */ + volatile uint32_t pio1_17; /*!< (@ 0x400440A4) I/O configuration for port pio1 */ + volatile uint32_t pio1_18; /*!< (@ 0x400440A8) I/O configuration for port pio1 */ + volatile uint32_t pio1_19; /*!< (@ 0x400440AC) I/O configuration for port pio1 */ + volatile uint32_t pio1_20; /*!< (@ 0x400440B0) I/O configuration for port pio1 */ + volatile uint32_t pio1_21; /*!< (@ 0x400440B4) I/O configuration for port pio1 */ + volatile uint32_t pio1_22; /*!< (@ 0x400440B8) I/O configuration for port pio1 */ + volatile uint32_t pio1_23; /*!< (@ 0x400440BC) I/O configuration for port pio1 */ + volatile uint32_t pio1_24; /*!< (@ 0x400440C0) I/O configuration for port pio1 */ + volatile uint32_t pio1_25; /*!< (@ 0x400440C4) I/O configuration for port pio1 */ + volatile uint32_t pio1_26; /*!< (@ 0x400440C8) I/O configuration for port pio1 */ + volatile uint32_t pio1_27; /*!< (@ 0x400440CC) I/O configuration for port pio1 */ + volatile uint32_t pio1_28; /*!< (@ 0x400440D0) I/O configuration for port pio1 */ + volatile uint32_t pio1_29; /*!< (@ 0x400440D4) I/O configuration for port pio1 */ + volatile uint32_t pio1_30; /*!< (@ 0x400440D8) I/O configuration for port pio1 */ + volatile uint32_t pio1_31; /*!< (@ 0x400440DC) I/O configuration for port pio1 */ + volatile const uint32_t reserved0[4]; + volatile uint32_t pio2_0; /*!< (@ 0x400440F0) I/O configuration for port pio2 */ + volatile uint32_t pio2_1; /*!< (@ 0x400440F4) I/O configuration for port pio2 */ + volatile const uint32_t reserved1; + volatile uint32_t pio2_2; /*!< (@ 0x400440FC) I/O configuration for port pio2 */ + volatile uint32_t pio2_3; /*!< (@ 0x40044100) I/O configuration for port pio2 */ + volatile uint32_t pio2_4; /*!< (@ 0x40044104) I/O configuration for port pio2 */ + volatile uint32_t pio2_5; /*!< (@ 0x40044108) I/O configuration for port pio2 */ + volatile uint32_t pio2_6; /*!< (@ 0x4004410C) I/O configuration for port pio2 */ + volatile uint32_t pio2_7; /*!< (@ 0x40044110) I/O configuration for port pio2 */ + volatile uint32_t pio2_8; /*!< (@ 0x40044114) I/O configuration for port pio2 */ + volatile uint32_t pio2_9; /*!< (@ 0x40044118) I/O configuration for port pio2 */ + volatile uint32_t pio2_10; /*!< (@ 0x4004411C) I/O configuration for port pio2 */ + volatile uint32_t pio2_11; /*!< (@ 0x40044120) I/O configuration for port pio2 */ + volatile uint32_t pio2_12; /*!< (@ 0x40044124) I/O configuration for port pio2 */ + volatile uint32_t pio2_13; /*!< (@ 0x40044128) I/O configuration for port pio2 */ + volatile uint32_t pio2_14; /*!< (@ 0x4004412C) I/O configuration for port pio2 */ + volatile uint32_t pio2_15; /*!< (@ 0x40044130) I/O configuration for port pio2 */ + volatile uint32_t pio2_16; /*!< (@ 0x40044134) I/O configuration for port pio2 */ + volatile uint32_t pio2_17; /*!< (@ 0x40044138) I/O configuration for port pio2 */ + volatile uint32_t pio2_18; /*!< (@ 0x4004413C) I/O configuration for port pio2 */ + volatile uint32_t pio2_19; /*!< (@ 0x40044140) I/O configuration for port pio2 */ + volatile uint32_t pio2_20; /*!< (@ 0x40044144) I/O configuration for port pio2 */ + volatile uint32_t pio2_21; /*!< (@ 0x40044148) I/O configuration for port pio2 */ + volatile uint32_t pio2_22; /*!< (@ 0x4004414C) I/O configuration for port pio2 */ + volatile uint32_t pio2_23; /*!< (@ 0x40044150) I/O configuration for port pio2 */ +}; +#define LPC_IOCON ((struct lpc_io_control *)LPC_IOCON_BASE) + +/* 6.5.2 pin control registers */ +#define IOCON_DAPIN_FUNC_MASK (7U) +#define IOCON_DAPIN_FUNC_SHIFT (0U) +#define IOCON_DAPIN_MODE_MASK (3U) +#define IOCON_DAPIN_MODE_SHIFT (3U) +#define IOCON_DAPIN_MODE_INACTIVE (0U) +#define IOCON_DAPIN_MODE_PULLDOWN (1U) +#define IOCON_DAPIN_MODE_PULLUP (2U) +#define IOCON_DAPIN_MODE_REPEATER (0U) +#define IOCON_DAPIN_HYS (1U << 5U) +#define IOCON_DAPIN_INV (1U << 6U) +#define IOCON_DAPIN_ADMODE (1U << 7U) +#define IOCON_DAPIN_SMODE_MASK (3U) +#define IOCON_DAPIN_SMODE_SHIFT (11U) +#define IOCON_DAPIN_SMODE_BYPASS (0U) +#define IOCON_DAPIN_CLKDIV_MASK (7U) +#define IOCON_DAPIN_CLKDIV_SHIFT (13U) + +#define IOCON_DPIN_FUNC_MASK (7U) +#define IOCON_DPIN_FUNC_SHIFT (0U) +#define IOCON_DPIN_MODE_MASK (3U) +#define IOCON_DPIN_MODE_SHIFT (3U) +#define IOCON_DPIN_MODE_INACTIVE (0U) +#define IOCON_DPIN_MODE_PULLDOWN (1U) +#define IOCON_DPIN_MODE_PULLUP (2U) +#define IOCON_DPIN_MODE_REPEATER (0U) +#define IOCON_DPIN_HYS (1U << 5U) +#define IOCON_DPIN_INV (1U << 6U) + +/* 7. General purpose i/o register */ +struct lpc_gpio_port { /*!< (@ 0xA0000000) Gpio_PORT Structure */ + volatile uint8_t byte[88]; /*!< (@ 0xA0000000) Byte pin registers */ + volatile const uint32_t reserved0[42]; + volatile uint32_t word[88]; /*!< (@ 0xA0000100) Word pin registers */ + volatile const uint32_t reserved1[1896]; + volatile uint32_t dir[3]; /*!< (@ 0xA0002000) Port Direction registers */ + volatile const uint32_t reserved2[29]; + volatile uint32_t mask[3]; /*!< (@ 0xA0002080) Port Mask register */ + volatile const uint32_t reserved3[29]; + volatile uint32_t pin[3]; /*!< (@ 0xA0002100) Port pin register */ + volatile const uint32_t reserved4[29]; + volatile uint32_t mpin[3]; /*!< (@ 0xA0002180) Masked port register */ + volatile const uint32_t reserved5[29]; + volatile uint32_t + set[3]; /*!< (@ 0xA0002200) Write: Set port register Read: port output bits */ + volatile const uint32_t reserved6[29]; + volatile uint32_t clr[3]; /*!< (@ 0xA0002280) Clear port */ + volatile const uint32_t reserved7[29]; + volatile uint32_t not[3]; /*!< (@ 0xA0002300) Toggle port */ +}; + +#define LPC_GPIO_PORT ((struct lpc_gpio_port *)LPC_GPIO_PORT_BASE) + +/* 18.6.1 configuration register */ +#define SCT_CONFIG_UNIFY (1U << 0U) +#define SCT_CONFIG_AUTOLIMIT_L (1U << 17U) +#define SCT_CONFIG_AUTOLIMIT_H (1U << 18U) + +/* 18.6.2 control register */ +#define SCT_CTRL_DOWN_L (1U << 0U) +#define SCT_CTRL_STOP_L (1U << 1U) +#define SCT_CTRL_HALT_L (1U << 2U) +#define SCT_CTRL_CLRCNTR_L (1U << 3U) +#define SCT_CTRL_BIDIR_L (1U << 4U) +#define SCT_CTRL_PRE_L_MASK (0x7f) +#define SCT_CTRL_PRE_L_SHIFT (5U) +#define SCT_CTRL_DOWN_H (1U << 16U) +#define SCT_CTRL_STOP_H (1U << 17U) +#define SCT_CTRL_HALT_H (1U << 18U) +#define SCT_CTRL_CLRCNTR_H (1U << 19U) +#define SCT_CTRL_BIDIR_H (1U << 20U) +#define SCT_CTRL_PRE_H_MASK (0x7f) +#define SCT_CTRL_PRE_H_SHIFT (21U) + +/* 18.6.11 output register */ +#define SCT_OUTPUT_OUT0 (1U << 0U) +#define SCT_OUTPUT_OUT1 (1U << 1U) +#define SCT_OUTPUT_OUT2 (1U << 2U) +#define SCT_OUTPUT_OUT3 (1U << 3U) + +/* 18.6.23 event state register */ +#define SCT_EV_STATE_MASK0 (1U << 0U) +#define SCT_EV_STATE_MASK1 (1U << 1U) +#define SCT_EV_STATE_MASK2 (1U << 2U) +#define SCT_EV_STATE_MASK3 (1U << 3U) +#define SCT_EV_STATE_MASK4 (1U << 4U) +#define SCT_EV_STATE_MASK5 (1U << 5U) +#define SCT_EV_STATE_MASK6 (1U << 6U) + +/* 18.6.24 sct event control register */ +#define SCT_EV_CTRL_COMBMODE_OR (0U << 12U) +#define SCT_EV_CTRL_COMBMODE_MATCH (1U << 12U) +#define SCT_EV_CTRL_COMBMODE_IO (2U << 12U) +#define SCT_EV_CTRL_COMBMODE_AND (3U << 12U) +#define SCT_EV_CTRL_OUTSEL (1U << 5U) +#define SCT_EV_CTRL_IOSEL_SHIFT (6U) +#define SCT_EV_CTRL_MATCHMEM (1U << 20U) + +/* 32-bit counters/timers */ +struct lpc_ct32 { /*!< (@ 0x40014000) CT32B0 Structure */ + volatile uint32_t ir; /*!< (@ 0x40014000) Interrupt Register. The IR can be written to */ + /* clear interrupts. The IR can be read to identify which of eight possible interrupt */ + /* sources are pending. */ + volatile uint32_t tcr; /*!< (@ 0x40014004) Timer Control Register. The TCR is used to */ + /* control the Timer Counter functions. The Timer Counter can be disabled or reset */ + /* through the TCR. */ + volatile uint32_t tc; /*!< (@ 0x40014008) Timer Counter. The 32-bit TC is incremented */ + /* every PR+1 cycles of PCLK. The TC is controlled through the TCR. */ + volatile uint32_t pr; /*!< (@ 0x4001400C) Prescale Register. When the Prescale Counter */ + /* (below) is equal to this value, the next clock increments the TC and clears the PC.*/ + volatile uint32_t pc; /*!< (@ 0x40014010) Prescale Counter. The 32-bit PC is a counter */ + /* which is incremented to the value stored in PR. When the value in PR is reached, */ + /* the TC is incremented and the PC is cleared. The PC is observable and */ + /* controllable through the bus interface. */ + volatile uint32_t mcr; /*!< (@ 0x40014014) Match Control Register. The MCR is used to */ + /* control if an interrupt is generated and if the TC is reset when a Match occurs. */ + volatile uint32_t mr0; /*!< (@ 0x40014018) Match Register 0. MR0 can be enabled through */ + /* the MCR to reset the TC, stop both the TC and PC, and/or generate an interrupt */ + /* every time MR0 matches the TC. */ + volatile uint32_t mr1; /*!< (@ 0x4001401C) Match Register 0. MR0 can be enabled through */ + /* the MCR to reset the TC, stop both the TC and PC, and/or generate an interrupt */ + /* every time MR0 matches the TC. */ + volatile uint32_t mr2; /*!< (@ 0x40014020) Match Register 0. MR0 can be enabled through */ + /* the MCR to reset the TC, stop both the TC and PC, and/or generate an interrupt */ + /* every time MR0 matches the TC. */ + volatile uint32_t mr3; /*!< (@ 0x40014024) Match Register 0. MR0 can be enabled through */ + /* the MCR to reset the TC, stop both the TC and PC, and/or generate an interrupt */ + /* every time MR0 matches the TC. */ + volatile uint32_t ccr; /*!< (@ 0x40014028) Capture Control Register. The CCR controls */ + /* which edges of the capture inputs are used to load the Capture Registers and */ + /* whether or not an interrupt is generated when a capture takes place. */ + volatile const uint32_t + cr0; /*!< (@ 0x4001402C) Capture Register 0. CR0 is loaded with the value */ + /* of TC when there is an event on the CT32B0_CAP0 input. */ + volatile const uint32_t reserved0; + volatile uint32_t + cr1; /*!< (@ 0x40014034) Capture Register 1. CR1 is loaded with the value */ + /* of TC when there is an event on the CT32B0_CAP1 input. */ + volatile const uint32_t reserved1; + volatile uint32_t emr; /*!< (@ 0x4001403C) External Match Register. The EMR controls the */ + /* match function and the external match pins CT32Bn_MAT[3:0]. */ + volatile const uint32_t reserved2[12]; + volatile uint32_t + ctcr; /*!< (@ 0x40014070) Count Control Register. The CTCR selects between */ + /* Timer and Counter mode, and in Counter mode selects the signal and edge(s) for */ + /* counting. */ + volatile uint32_t pwmc; /*!< (@ 0x40014074) PWM Control Register. The PWMCON enables PWM */ + /* mode for the external match pins CT32Bn_MAT[3:0]. */ +}; +#define LPC_CT32B0 ((struct lpc_ct32 *)LPC_CT32B0_BASE) +#define LPC_CT32B1 ((struct lpc_ct32 *)LPC_CT32B1_BASE) + +/* 20.6.1 interrupt register */ +#define CT32_IR_MR0INT (1U << 0U) +#define CT32_IR_MR1INT (1U << 1U) +#define CT32_IR_MR2INT (1U << 2U) +#define CT32_IR_MR3INT (1U << 3U) +#define CT32_IR_CR0INT (1U << 4U) +#define CT32_IR_CR1INT (1U << 5U) +#define CT32_IR_CR2INT (1U << 6U) + +/* 20.6.6 match control register */ +#define CT32_MCR_MR0I (1U << 0U) +#define CT32_MCR_MR0R (1U << 1U) +#define CT32_MCR_MR0S (1U << 2U) +#define CT32_MCR_MR1I (1U << 3U) +#define CT32_MCR_MR1R (1U << 4U) +#define CT32_MCR_MR1S (1U << 5U) +#define CT32_MCR_MR2I (1U << 6U) +#define CT32_MCR_MR2R (1U << 7U) +#define CT32_MCR_MR2S (1U << 8U) +#define CT32_MCR_MR3I (1U << 9U) +#define CT32_MCR_MR3R (1U << 10U) +#define CT32_MCR_MR3S (1U << 11U) + +/* 20.6.8 capture control register */ +#define CT32_CCR_CAP0RE (1U << 0U) +#define CT32_CCR_CAP0FE (1U << 1U) +#define CT32_CCR_CAP0I (1U << 2U) +#define CT32_CCR_CAP1RE (1U << 3U) +#define CT32_CCR_CAP1FE (1U << 4U) +#define CT32_CCR_CAP1I (1U << 5U) +#define CT32_CCR_CAP2RE (1U << 6U) +#define CT32_CCR_CAP2FE (1U << 7U) +#define CT32_CCR_CAP2I (1U << 8U) + +/* 20.6.10 external match register */ +#define CT32_EMR_EM0 (1U << 0U) +#define CT32_EMR_EM1 (1U << 1U) +#define CT32_EMR_EM2 (1U << 2U) +#define CT32_EMR_EM3 (1U << 3U) +#define CT32_EMR_EMCTR_NOTHING (0U) +#define CT32_EMR_EMCTR_CLEAR (1U) +#define CT32_EMR_EMCTR_SET (2U) +#define CT32_EMR_EMCTR_TOGGLE (3U) +#define CT32_EMR_EMC0_SHIFT (4U) +#define CT32_EMR_EMC1_SHIFT (6U) +#define CT32_EMR_EMC2_SHIFT (8U) +#define CT32_EMR_EMC3_SHIFT (10U) + +/* 20.6.2 timer control register */ +#define CT32_TCR_CEN (1U << 0U) +#define CT32_TCR_CRST (1U << 1U) + +/* 35.1.1 pin functions */ +#define IOCON_PIN0_11_FUNC_TDI (0U) +#define IOCON_PIN0_11_FUNC_PIO (1U) +#define IOCON_PIN0_11_FUNC_ADC (2U) +#define IOCON_PIN0_11_FUNC_MAT3 (3U) +#define IOCON_PIN0_11_FUNC_RTS (4U) +#define IOCON_PIN0_11_FUNC_SCLK (5U) +#define IOCON_PIN0_12_FUNC_TMS (0U) +#define IOCON_PIN0_12_FUNC_PIO (1U) +#define IOCON_PIN0_12_FUNC_ADC (2U) +#define IOCON_PIN0_12_FUNC_CAP0 (3U) +#define IOCON_PIN0_12_FUNC_CTS (4U) +#define IOCON_PIN1_13_FUNC_PIO (0U) +#define IOCON_PIN1_13_FUNC_CTS (1U) +#define IOCON_PIN1_13_FUNC_OUT3 (2U) +#define IOCON_PIN1_19_FUNC_PIO (0U) +#define IOCON_PIN1_19_FUNC_CTS (1U) +#define IOCON_PIN1_19_FUNC_OUT0 (2U) +#define IOCON_PIN2_2_FUNC_PIO (0U) +#define IOCON_PIN2_2_FUNC_RTS (1U) +#define IOCON_PIN2_2_FUNC_SCLK (2U) +#define IOCON_PIN2_2_FUNC_OUT1 (3U) +#define IOCON_PIN2_7_FUNC_PIO (0U) +#define IOCON_PIN2_7_FUNC_SCK (1U) +#define IOCON_PIN2_7_FUNC_OUT2 (2U) +#define IOCON_PIN2_16_FUNC_PIO (3U) +#define IOCON_PIN2_16_FUNC_OUT0 (1U) +#define IOCON_PIN2_17_FUNC_PIO (0U) +#define IOCON_PIN2_17_FUNC_OUT1 (1U) +#define IOCON_PIN2_18_FUNC_PIO (0U) +#define IOCON_PIN2_18_FUNC_OUT2 (1U) + +#endif /* ZEPHYR_DRIVERS_DALI_LPC11U6X_H_ */ diff --git a/drivers/dali/timings.h b/drivers/dali/timings.h new file mode 100644 index 000000000000..ad533eeacee9 --- /dev/null +++ b/drivers/dali/timings.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2025 by Sven Hädrich + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_DALI_TIMING_H_ +#define ZEPHYR_DRIVERS_DALI_TIMING_H_ + +/* timing definitions given by the DALI standard - for use by DALI driver implementations */ + +/* IEC 62396-101:2022 4.10 Table 4 Power interruptions */ +#define DALI_FAILURE_CONDITION_US (550000U) /* 550 ms */ + +/* IEC 62386-101:2022 7.4 Frame types */ +#define DALI_MAX_BIT_PER_FRAME (32U) +#define DALI_FRAME_BACKWARD_LENGTH (8U) +#define DALI_FRAME_GEAR_LENGTH (16U) +#define DALI_FRAME_DEVICE_LENGTH (24U) +#define DALI_FRAME_UPDATE_LENGTH (32U) + +/* IEC 62386-101:2022 8.2.1 Receiver bit timing - Table 18 */ +#define DALI_RX_BIT_TIME_HALF_MIN_US (333U) +#define DALI_RX_BIT_TIME_HALF_MAX_US (500U) +#define DALI_RX_BIT_TIME_FULL_MIN_US (667U) +#define DALI_RX_BIT_TIME_FULL_MAX_US (1000U) +#define DALI_RX_BIT_TIME_STOP_US (2400U) + +/* IEC 62386-101:2022 8.2.3 Receiver settling times - Table 20 */ +#define DALI_RX_FORWARD_BACK_MAX_US (12400U) +#define DALI_RX_TWICE_MIN_US (2400U) +#define DALI_RX_TWICE_MAX_US (94000U) + +/* IEC 62386-101:2022 8.3.1 Multi-master transmitter bit timing */ +#define DALI_TX_HALF_BIT_MIN_US (400U) +#define DALI_TX_HALF_BIT_US (417U) +#define DALI_TX_HALF_BIT_MAX_US (433U) +#define DALI_TX_FULL_BIT_MIN_US (800U) +#define DALI_TX_FULL_BIT_US (833U) +#define DALI_TX_FULL_BIT_MAX_US (867U) +#define DALI_TX_STOP_BIT_US (2450U) + +/* IEC 62386-101:2022 8.3.2 Multi-master transmitter settling time values */ +#define DALI_TX_BACKWARD_INTERFRAME_MIN_US (5500U) +#define DALI_TX_BACKWARD_INTERFRAME_MAX_US (10500U) +#define DALI_TX_PRIO_1_INTERFRAME_MIN_US (13500U) +#define DALI_TX_PRIO_1_INTERFRAME_MAX_US (14700U) +#define DALI_TX_PRIO_2_INTERFRAME_MIN_US (14900U) +#define DALI_TX_PRIO_2_INTERFRAME_MAX_US (16100U) +#define DALI_TX_PRIO_3_INTERFRAME_MIN_US (16300U) +#define DALI_TX_PRIO_3_INTERFRAME_MAX_US (17700U) +#define DALI_TX_PRIO_4_INTERFRAME_MIN_US (17900U) +#define DALI_TX_PRIO_4_INTERFRAME_MAX_US (19300U) +#define DALI_TX_PRIO_5_INTERFRAME_MIN_US (19500U) +#define DALI_TX_PRIO_5_INTERFRAME_MAX_US (21100U) + +/* IEC 62386-101:2022 9.2.3 Collision detroy areas */ +#define DALI_TX_DESTROY_1_MIN_US (100U) +#define DALI_TX_DESTROY_1_MAX_US (357U) +#define DALI_TX_DESTROY_2_MIN_US (477U) +#define DALI_TX_DESTROY_2_MAX_US (723U) +#define DALI_TX_DESTROY_3_MIN_US (943U) + +/* IEC 62386-101:2022 9.2.4 Collision recovery timing */ +#define DALI_TX_BREAK_MIN_US (1200U) +#define DALI_TX_BREAK_MAX_US (1400U) +#define DALI_TX_RECOVER_MIN_US (4000U) +#define DALI_TX_RECOVER_MAX_US (4600U) + +/* IEC 62386-101:2022 9.6.2 Shared interface - backward frame */ +#define DALI_TX_CORRUPT_BIT_MIN_US (1300U) +#define DALI_TX_CORRUPT_BIT_MAX_US (2000U) + +#endif /* ZEPHYR_DRIVERS_DALI_TIMING_H_ */ diff --git a/dts/bindings/dali/nxp,dali-lpc11u6x.yaml b/dts/bindings/dali/nxp,dali-lpc11u6x.yaml new file mode 100644 index 000000000000..a01f86029bcd --- /dev/null +++ b/dts/bindings/dali/nxp,dali-lpc11u6x.yaml @@ -0,0 +1,41 @@ +--- +description: DALI interface for nxp lpc11u6x + +compatible: "nxp,dali-lpc11u6x" + +include: base.yaml + +properties: + tx-rise-fall-delta-us: + type: int + required: false + description: | + The time difference of the delays for falling versus the delay for + rising edges when transmitting DALI frames. This value needs to be + set to transmit with correct bit timings. + The delay is measured in micro seconds. + + rx-rise-fall-delta-us: + type: int + required: false + description: | + The time difference of the delays for falling versus the delay for + rising edges when receiving DALI frames. This value needs to be set + for correct identification of legal bit timings. + The delay is measured in micro seconds. + + tx-rx-propagation-min-us: + type: int + required: false + description: | + The minimal time from a transmitted change to read observation. This + value needs to be set for proper collision detection. + The delay is measured in micro seconds. + + tx-rx-propagation-max-us: + type: int + required: false + description: | + The maximal time from a transmitted change to read observation. + This value needs to be set for proper collision detection. + The delay is measured in micro seconds. diff --git a/dts/bindings/dali/zephyr,dali-pwm.yaml b/dts/bindings/dali/zephyr,dali-pwm.yaml new file mode 100644 index 000000000000..fa9a633e657f --- /dev/null +++ b/dts/bindings/dali/zephyr,dali-pwm.yaml @@ -0,0 +1,54 @@ +description: PWM based DALI interface + +compatible: "zephyr,dali-pwm" + +include: base.yaml + +properties: + pwms: + type: phandle-array + required: true + + tx-flank-shift-us: + type: int + description: | + Difference in propagation delay in ns between the falling and rising edge. + If for example the high to low delay is 8 us and low to high is 22 us, then enter + (-14) here. Keep in low µs range. + required: false + + rx-flank-shift-us: + type: int + description: | + Difference in propagation delay in ns between the falling and rising edge. + If for example the high to low delay is 47 us and low to high is 15 us, then enter + (32) here. Keep in low µs range. + required: false + + tx-rx-propagation-max-us: + type: int + required: false + description: | + The maximal time from a transmitted change to read observation. + This value needs to be set for proper collision detection. + The delay is measured in micro seconds. + + rx-finish-work-delay-us: + type: int + required: false + description: | + This is the time that elapses from the time the stop bit period has + expired until the corresponding workqueue processing has started. + This time depends on the clockrate and architecture of the target system. + This parameter might need adjustment to pass the test item 3.15 of + the DALI tests. The delay is measured in micro seconds. + + rx-capture-work-delay-us: + type: int + required: false + description: | + This is time that elapses from the moment a DALI bus event is + captured until the corresponding workqueue pricessing has started. + This time depends on the clockrate and architecture of the target system. + This parameter might need adjustment to pass the test item 3.14 of + the DALI tests. The delay is measured in micro seconds. diff --git a/include/zephyr/drivers/dali.h b/include/zephyr/drivers/dali.h new file mode 100644 index 000000000000..d4c100a07ac3 --- /dev/null +++ b/include/zephyr/drivers/dali.h @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2025 by Sven Hädrich + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_DALI_H_ +#define ZEPHYR_INCLUDE_DRIVERS_DALI_H_ + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief DALI frame and event types + */ +enum dali_event_type { + DALI_EVENT_NONE, /**< no event (write/receive) */ + DALI_FRAME_CORRUPT, /**< corrupt frame (write/receive)*/ + DALI_FRAME_BACKWARD, /**< backward frame (write/receive) */ + DALI_FRAME_GEAR, /**< forward 16bit gear frame (write/receive)*/ + DALI_FRAME_GEAR_TWICE, /**< forward 16bit gear frame, received twice (receive) */ + DALI_FRAME_DEVICE, /**< forward 24bit device frame (write/receive) */ + DALI_FRAME_DEVICE_TWICE, /**< forward 24bit device, received twice (receive)*/ + DALI_FRAME_FIRMWARE, /**< forward 32bit firmware frame (write/receive) */ + DALI_FRAME_FIRMWARE_TWICE, /**< forward 32bit firmware frame (receive) */ + DALI_EVENT_NO_ANSWER, /**< received no reply (receive) */ + DALI_EVENT_BUS_FAILURE, /**< detected a bus failure (receive) */ + DALI_EVENT_BUS_IDLE, /**< detected that bus is idle again after failure (receive) */ +}; + +/** + * @brief Frame transmission inter-frame priorities. + * see IEC 62386-101:2022 Table 22 -- Multi-master transmitter settling time values * + */ +enum dali_tx_priority { + DALI_PRIORITY_BACKWARD_FRAME = 0, + DALI_PRIORITY_1 = 1, + DALI_PRIORITY_2 = 2, + DALI_PRIORITY_3 = 3, + DALI_PRIORITY_4 = 4, + DALI_PRIORITY_5 = 5, +}; + +/** DALI frame */ +struct dali_frame { + uint32_t data; /**< LSB aligned payload */ + enum dali_event_type event_type; /**< event type of frame */ +}; + +/** DALI send frame */ +struct dali_tx_frame { + struct dali_frame frame; /**< payload frame */ + enum dali_tx_priority priority; /**< inter frame timing */ + bool is_query; /**< frame is a query, initiate reception of backward frame */ +}; + +/** + * @brief DALI driver API + * + * This is the mandatory API any driver needs to expose. + */ +__subsystem struct dali_driver_api { + int (*receive)(const struct device *dev, struct dali_frame *frame, k_timeout_t timeout); + int (*send)(const struct device *dev, const struct dali_tx_frame *frame); + void (*abort)(const struct device *dev); +}; + +/** + * @brief Receive a frame or event from DALI bus. + * + * All valid frames received on DALI bus are delivered by this function. + * + * The caller is responsible to process incoming frames in a timely manner. + * The queue size is small and if not accessed fast enough, frames are silently + * dropped. + * Bus events are also reported via this function as dali_frames. + * The data on event frames should be ignored. + * + * @param[in] dev DALI device + * @param[out] rx_frame received DALI frame, or event + * @param[in] timeout kernel timeout or one of K_NO_WAIT or K_FOREVER + * + * @retval 0 success, report was stored + * @retval -ENOMSG returned without waiting or waiting period timed out + * @retval -EINVAL invalid input parameters + */ +static inline int dali_receive(const struct device *dev, struct dali_frame *rx_frame, + k_timeout_t timeout) +{ + const struct dali_driver_api *api = dev->api; + + __ASSERT_NO_MSG(dev); + __ASSERT_NO_MSG(rx_frame); + return api->receive(dev, rx_frame, timeout); +} + +/** + * @brief Send a frame on DALI bus. + * + * This function supports async operation. Any frame is stored into an internal send slot and the + * dali_send returns immediately. dali_send maintains two send slots. One slot is reserved for + * backward frames. The other slot is used for all kind of forward frames. In case of a forward + * frame in its slot that is pending for transmission, it is still possible to provide a backward + * frame. That backward frame will be transmitted before the pending forward frame whenever + * possible. There is a strict timing limit from the DALI standard (see IEC 62386-101:2022 8.1.2 + * Table 17) for the timing of backward frames. When these restrictions can not be fulfilled, the + * backward frame may be dropped and an error code returned. + * + * @param[in] dev DALI device + * @param[in] tx_frame DALI frame to transmit + * + * @retval 0 success + * @retval -EINVAL invalid input parameters + * @retval -ETIMEDOUT backward frame cannot be send any more within DALI standard + * @retval -EBUSY send queue is full. No more memory for more messages, try later + */ +static inline int dali_send(const struct device *dev, const struct dali_tx_frame *tx_frame) +{ + const struct dali_driver_api *api = dev->api; + + __ASSERT_NO_MSG(dev); + __ASSERT_NO_MSG(tx_frame); + return api->send(dev, tx_frame); +} + +/** + * @brief Abort sending forward frames + * + * This will abort all pending or ongoing forward frame transmissions. Transmission + * will be aborted, regardless of bit timings, at the shortest possible time. + * This can result in corrupt a frame. + * + * @param[in] dev DALI device + */ +static inline void dali_abort(const struct device *dev) +{ + const struct dali_driver_api *api = dev->api; + + __ASSERT_NO_MSG(dev); + api->abort(dev); +} + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_DRIVERS_DALI_H_ */ diff --git a/samples/drivers/dali/CMakeLists.txt b/samples/drivers/dali/CMakeLists.txt new file mode 100644 index 000000000000..587d58174d56 --- /dev/null +++ b/samples/drivers/dali/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(dali_app LANGUAGES C) +target_sources(app PRIVATE src/main.c) diff --git a/samples/drivers/dali/README.rst b/samples/drivers/dali/README.rst new file mode 100644 index 000000000000..2c0222888c89 --- /dev/null +++ b/samples/drivers/dali/README.rst @@ -0,0 +1,60 @@ +.. zephyr:code-sample:: dali + :name: Digital Addressable Lighting Interface (DALI) + :relevant-api: dali + + Blink LED controllers connected to a DALI bus. + +Overview +******** + +This sample utilizes the :ref:`dali ` driver API to blink DALI enabled LED drivers. + +Building and Running +******************** + +The interface to the DALI bus is defined in the board's devicetree. + +The board's devictree must have a ``dali0`` node that provides the +access to the DALI bus. See the predefined overlays in +:zephyr_file:`samples/drivers/dali/boards` for examples. + +.. note:: For proper operation a DALI specific physcial interface is required. + +Building and Running for ST Nucleo F091RC +========================================= +The :zephyr_file:`samples/drivers/dali/boards/nucleo_f091rc.overlay` +is specifically for the Mikroe-2672 DALI2 click development board +used as physical interface to the DALI bus. This board uses negative +logic for signal transmission (Tx Low <-> DALI Bus Idle). +The sample can be build an executed for the +:zephyr:board:`nucleo_f091rc` as follows: + +.. zephyr-app-commands:: + :zephyr-app: samples/drivers/dali + :board: nucleo_f091rc + :goals: build flash + :compact: + +Building and Running for NXP LPCXpresso11U68 +============================================ +The :zephyr_file:`samples/drivers/dali/boards/lpcxpresso11u68.overlay` +is specifically for the Mikroe-2672 DALI2 click development board +used as physical interface to the DALI bus. This board uses negative +logic for signal transmission (Tx Low <-> DALI Bus Idle). +The sample can be build and executed for the +:zephyr:board:`lpcxpresso11u68` as follows: + +.. zephyr-app-commands:: + :zephyr-app: samples/drivers/dali + :board: lpcxpresso11u68 + :goals: build flash + :compact: + +Sample outout +============= + +You should see DALI frames transferred every 2 seconds to the DALI bus. +The frames are alternating. One frame is a DALI OFF command broadcasted to +all control gears. The other frame is a DALI RECALL MAX command broadcasted +to all control gears. When a control gear is connected it will alternate +between no light output from the attached LED and maximum output of the LED. \ No newline at end of file diff --git a/samples/drivers/dali/boards/lpcxpresso11u68.overlay b/samples/drivers/dali/boards/lpcxpresso11u68.overlay new file mode 100644 index 000000000000..aebbbfa22f4f --- /dev/null +++ b/samples/drivers/dali/boards/lpcxpresso11u68.overlay @@ -0,0 +1,17 @@ +/* add the DALI-interfacxe via click-adapter board */ +/* connect DALI Tx to port 0.11 */ +/* connect DALI Rx to port 0.12 */ + +/ { + dali0: dali { + compatible = "nxp,dali-lpc11u6x"; + status = "okay"; + interrupt-parent = <&nvic>; + #interrupt-cells = <2>; + interrupts = <18 1>, <19 1>; + tx-rise-fall-delta-us = <(-44)>; + rx-rise-fall-delta-us = <(-56)>; + tx-rx-propagation-min-us = <90>; + tx-rx-propagation-max-us = <105>; + }; +}; diff --git a/samples/drivers/dali/boards/nucleo_f091rc.overlay b/samples/drivers/dali/boards/nucleo_f091rc.overlay new file mode 100644 index 000000000000..76c7e66f6ea0 --- /dev/null +++ b/samples/drivers/dali/boards/nucleo_f091rc.overlay @@ -0,0 +1,41 @@ +/* add the DALI-interface via Click-adapter-board */ +/* connect DALI Tx to D12 */ +/* connect DALI Rx to A0 */ + +/ { + dali0: dali { + compatible = "zephyr,dali-pwm"; + status = "okay"; + pwms = <&pwm3 1 PWM_USEC(41) PWM_POLARITY_NORMAL>, + <&pwm2 1 PWM_USEC(41) PWM_POLARITY_INVERTED>; + tx-flank-shift-us = <(-46)>; + rx-flank-shift-us = <(-56)>; + tx-rx-propagation-max-us = <2400>; + rx-finish-work-delay-us = <1000>; + rx-capture-work-delay-us = <200>; + }; +}; + +&timers3 { + // set timebase to 1us/1MHz + st,prescaler = <47>; + status = "okay"; + + pwm3: pwm { + pinctrl-0 = <&tim3_ch1_pa6>; + pinctrl-names = "default"; + status = "okay"; + }; +}; + +&timers2 { + // set timebase to 1us/1MHz + st,prescaler = <47>; + status = "okay"; + + pwm2: pwm { + pinctrl-0 = <&tim2_ch1_pa0>; + pinctrl-names = "default"; + status = "okay"; + }; +}; diff --git a/samples/drivers/dali/debug.conf b/samples/drivers/dali/debug.conf new file mode 100644 index 000000000000..2722464c08cd --- /dev/null +++ b/samples/drivers/dali/debug.conf @@ -0,0 +1,19 @@ +# manage output stream +CONFIG_USE_SEGGER_RTT=y + +# logging +CONFIG_LOG=y +CONFIG_LOG_MODE_IMMEDIATE=y +CONFIG_LOG_PRINTK=y +CONFIG_LOG_BACKEND_UART=n +CONFIG_LOG_BACKEND_RTT=y +CONFIG_LOG_BACKEND_SHOW_COLOR=n +CONFIG_LOG_BACKEND_RTT_MODE_DROP=y +CONFIG_DALI_LOW_LEVEL_LOG_LEVEL_DBG=y + +# debugger support +CONFIG_DEBUG_OPTIMIZATIONS=y +CONFIG_THREAD_MONITOR=y +CONFIG_ASSERT=y +CONFIG_THREAD_NAME=y +CONFIG_THREAD_ANALYZER=y diff --git a/samples/drivers/dali/prj.conf b/samples/drivers/dali/prj.conf new file mode 100644 index 000000000000..aab2eb9831fd --- /dev/null +++ b/samples/drivers/dali/prj.conf @@ -0,0 +1 @@ +CONFIG_DALI=y diff --git a/samples/drivers/dali/sample.yaml b/samples/drivers/dali/sample.yaml new file mode 100644 index 000000000000..e3174ceeca9e --- /dev/null +++ b/samples/drivers/dali/sample.yaml @@ -0,0 +1,14 @@ +sample: + description: Dali Zephyr driver application + name: dali-zephyr +common: + build_only: true + integration_platforms: + - nucleo_f091rc + - dali_dk_lpc11u68 + - nrf52840dk/nrf52840 +tests: + app.default: {} + app.debug: + extra_overlay_confs: + - debug.conf diff --git a/samples/drivers/dali/src/main.c b/samples/drivers/dali/src/main.c new file mode 100644 index 000000000000..9e9deee56a2a --- /dev/null +++ b/samples/drivers/dali/src/main.c @@ -0,0 +1,55 @@ +/* +* Copyright (c) 2025 by Sven Hädrich +* SPDX-License-Identifier: Apache-2.0 +*/ + +#include + +#include +#include +#include + +#include +LOG_MODULE_REGISTER(main); + +#define DALI_NODE DT_NODELABEL(dali0) + +int main(void) +{ + LOG_INF("DALI Blinky"); + LOG_INF("Target board: %s", CONFIG_BOARD_TARGET); + + /* get the DALI device */ + const struct device *dali_dev = DEVICE_DT_GET(DALI_NODE); + if (!device_is_ready(dali_dev)) { + LOG_ERR("failed to get DALI device."); + return 0; + } + + /* prepare on / off frame */ + const struct dali_tx_frame frame_recall_max = (struct dali_tx_frame){ + .frame = + (struct dali_frame){ + .event_type = DALI_FRAME_GEAR, + .data = 0xff05, + }, + .priority = 2, + }; + const struct dali_tx_frame frame_off = (struct dali_tx_frame){ + .frame = + (struct dali_frame){ + .event_type = DALI_FRAME_GEAR, + .data = 0xff00, + }, + .priority = 2, + }; + + /* send on / off DALI frames */ + for (;;) { + dali_send(dali_dev, &frame_recall_max); + k_sleep(K_MSEC(2000)); + dali_send(dali_dev, &frame_off); + k_sleep(K_MSEC(2000)); + } + return 0; +}