From aa3f46cbbfd6c9400a511d898a4bf2471d6e9a72 Mon Sep 17 00:00:00 2001 From: Michael Zimmermann Date: Sun, 14 May 2023 09:18:50 +0200 Subject: [PATCH] drivers: serial: Add initial SiM3U1xx support This supports polling and interrupt APIs, but not the async API. Signed-off-by: Michael Zimmermann --- drivers/serial/CMakeLists.txt | 1 + drivers/serial/Kconfig | 2 + drivers/serial/Kconfig.si32 | 10 + drivers/serial/uart_si32_usart.c | 395 +++++++++++++++++++++++++++++++ dts/arm/silabs/sim3u.dtsi | 16 ++ 5 files changed, 424 insertions(+) create mode 100644 drivers/serial/Kconfig.si32 create mode 100644 drivers/serial/uart_si32_usart.c diff --git a/drivers/serial/CMakeLists.txt b/drivers/serial/CMakeLists.txt index bbb7da14a99..946f00cfddf 100644 --- a/drivers/serial/CMakeLists.txt +++ b/drivers/serial/CMakeLists.txt @@ -76,6 +76,7 @@ zephyr_library_sources_ifdef(CONFIG_UART_RENESAS_RA uart_renesas_ra.c) zephyr_library_sources_ifdef(CONFIG_UART_ENE_KB1200 uart_ene_kb1200.c) zephyr_library_sources_ifdef(CONFIG_UART_RZT2M uart_rzt2m.c) zephyr_library_sources_ifdef(CONFIG_UART_RA8_SCI_B uart_renesas_ra8_sci_b.c) +zephyr_library_sources_ifdef(CONFIG_UART_SI32_USART uart_si32_usart.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE uart_handlers.c) zephyr_library_sources_ifdef(CONFIG_UART_SCI_RA uart_renesas_ra_sci.c) diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index ef557d090fc..cb4207d1200 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -280,4 +280,6 @@ rsource "Kconfig.rzt2m" rsource "Kconfig.renesas_ra8" +source "drivers/serial/Kconfig.si32" + endif # SERIAL diff --git a/drivers/serial/Kconfig.si32 b/drivers/serial/Kconfig.si32 new file mode 100644 index 00000000000..8a36f06f308 --- /dev/null +++ b/drivers/serial/Kconfig.si32 @@ -0,0 +1,10 @@ +# Copyright (c) 2024 GARDENA GmbH +# +# SPDX-License-Identifier: Apache-2.0 + +config UART_SI32_USART + bool "SI32 USART MCU serial driver" + default y + depends on DT_HAS_SILABS_SI32_USART_ENABLED + select SERIAL_HAS_DRIVER + select SERIAL_SUPPORT_INTERRUPT diff --git a/drivers/serial/uart_si32_usart.c b/drivers/serial/uart_si32_usart.c new file mode 100644 index 00000000000..a5151318803 --- /dev/null +++ b/drivers/serial/uart_si32_usart.c @@ -0,0 +1,395 @@ +/* + * Copyright (c) 2024 GARDENA GmbH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT silabs_si32_usart + +#include +#include +#include +#include +#include + +#include +#include +#include + +struct usart_si32_config { + SI32_USART_A_Type *usart; + bool hw_flow_control; + uint8_t parity; +#if defined(CONFIG_UART_INTERRUPT_DRIVEN) + uart_irq_config_func_t irq_config_func; +#endif + const struct device *clock_dev; +}; + +struct usart_si32_data { + uint32_t baud_rate; +#ifdef CONFIG_UART_INTERRUPT_DRIVEN + uart_irq_callback_user_data_t callback; + void *cb_data; +#endif +}; + +static int usart_si32_poll_in(const struct device *dev, unsigned char *c) +{ + const struct usart_si32_config *config = dev->config; + int ret = -1; + + if (SI32_USART_A_read_rx_fifo_count(config->usart) != 0) { + *c = SI32_USART_A_read_data_u8(config->usart); + ret = 0; + } + + return ret; +} + +static void usart_si32_poll_out(const struct device *dev, unsigned char c) +{ + const struct usart_si32_config *config = dev->config; + + while (SI32_USART_A_read_tx_fifo_count(config->usart) || + SI32_USART_A_is_tx_busy(config->usart)) { + /* busy wait */ + } + + SI32_USART_A_write_data_u8(config->usart, c); +} + +static int usart_si32_err_check(const struct device *dev) +{ + const struct usart_si32_config *config = dev->config; + int ret = 0; + + if (SI32_USART_A_is_tx_fifo_error_interrupt_pending(config->usart)) { + SI32_USART_A_clear_tx_fifo_error_interrupt(config->usart); + } + + if (SI32_USART_A_is_rx_overrun_interrupt_pending(config->usart)) { + SI32_USART_A_clear_rx_overrun_error_interrupt(config->usart); + ret |= UART_ERROR_OVERRUN; + } + + if (SI32_USART_A_is_rx_parity_error_interrupt_pending(config->usart)) { + SI32_USART_A_clear_rx_parity_error_interrupt(config->usart); + ret |= UART_ERROR_PARITY; + } + + if (SI32_USART_A_is_rx_frame_error_interrupt_pending(config->usart)) { + SI32_USART_A_clear_rx_frame_error_interrupt(config->usart); + ret |= UART_ERROR_FRAMING; + } + + return ret; +} + +#ifdef CONFIG_UART_INTERRUPT_DRIVEN +static int usart_si32_fifo_fill(const struct device *dev, const uint8_t *tx_data, int size) +{ + const struct usart_si32_config *config = dev->config; + int i; + + /* NOTE: Checking `SI32_USART_A_is_tx_busy` is a workaround. + * For some reason data gets corrupted when writing to the FIFO + * while a write is happening. + */ + for (i = 0; i < size && SI32_USART_A_read_tx_fifo_count(config->usart) == 0 && + !SI32_USART_A_is_tx_busy(config->usart); + i++) { + SI32_USART_A_write_data_u8(config->usart, tx_data[i]); + } + + return i; +} + +static int usart_si32_fifo_read(const struct device *dev, uint8_t *rx_data, const int size) +{ + const struct usart_si32_config *config = dev->config; + int i; + + for (i = 0; i < size; i++) { + if (!SI32_USART_A_read_rx_fifo_count(config->usart)) { + break; + } + + rx_data[i] = SI32_USART_A_read_data_u8(config->usart); + } + + return i; +} + +static void usart_si32_irq_tx_enable(const struct device *dev) +{ + const struct usart_si32_config *config = dev->config; + + SI32_USART_A_enable_tx_data_request_interrupt(config->usart); +} + +static void usart_si32_irq_tx_disable(const struct device *dev) +{ + const struct usart_si32_config *config = dev->config; + + SI32_USART_A_disable_tx_data_request_interrupt(config->usart); +} + +static int usart_si32_irq_tx_ready(const struct device *dev) +{ + const struct usart_si32_config *config = dev->config; + + return SI32_USART_A_is_tx_data_request_interrupt_pending(config->usart); +} + +static int usart_si32_irq_tx_complete(const struct device *dev) +{ + const struct usart_si32_config *config = dev->config; + + return SI32_USART_A_is_tx_complete(config->usart); +} + +static void usart_si32_irq_rx_enable(const struct device *dev) +{ + const struct usart_si32_config *config = dev->config; + + SI32_USART_A_enable_rx_data_request_interrupt(config->usart); +} + +static void usart_si32_irq_rx_disable(const struct device *dev) +{ + const struct usart_si32_config *config = dev->config; + + SI32_USART_A_disable_rx_data_request_interrupt(config->usart); +} + +static int usart_si32_irq_rx_ready(const struct device *dev) +{ + const struct usart_si32_config *config = dev->config; + + return SI32_USART_A_is_rx_data_request_interrupt_pending(config->usart); +} + +static void usart_si32_irq_err_enable(const struct device *dev) +{ + const struct usart_si32_config *config = dev->config; + + SI32_USART_A_enable_rx_error_interrupts(config->usart); + SI32_USART_A_enable_tx_error_interrupts(config->usart); +} + +static void usart_si32_irq_err_disable(const struct device *dev) +{ + const struct usart_si32_config *config = dev->config; + + SI32_USART_A_disable_rx_error_interrupts(config->usart); + SI32_USART_A_disable_tx_error_interrupts(config->usart); +} + +static int usart_si32_irq_is_pending(const struct device *dev) +{ + return usart_si32_irq_rx_ready(dev) || usart_si32_irq_tx_ready(dev); +} + +static int usart_si32_irq_update(const struct device *dev) +{ + ARG_UNUSED(dev); + + return 1; +} + +static void usart_si32_irq_callback_set(const struct device *dev, uart_irq_callback_user_data_t cb, + void *cb_data) +{ + struct usart_si32_data *data = dev->data; + + data->callback = cb; + data->cb_data = cb_data; +} + +static void usart_si32_irq_handler(const struct device *dev) +{ + struct usart_si32_data *data = dev->data; + + if (data->callback) { + data->callback(dev, data->cb_data); + } + + usart_si32_err_check(dev); +} +#endif /* CONFIG_UART_INTERRUPT_DRIVEN */ + +static const struct uart_driver_api usart_si32_driver_api = { + .poll_in = usart_si32_poll_in, + .poll_out = usart_si32_poll_out, + .err_check = usart_si32_err_check, +#ifdef CONFIG_UART_INTERRUPT_DRIVEN + .fifo_fill = usart_si32_fifo_fill, + .fifo_read = usart_si32_fifo_read, + .irq_tx_enable = usart_si32_irq_tx_enable, + .irq_tx_disable = usart_si32_irq_tx_disable, + .irq_tx_ready = usart_si32_irq_tx_ready, + .irq_tx_complete = usart_si32_irq_tx_complete, + .irq_rx_enable = usart_si32_irq_rx_enable, + .irq_rx_disable = usart_si32_irq_rx_disable, + .irq_rx_ready = usart_si32_irq_rx_ready, + .irq_err_enable = usart_si32_irq_err_enable, + .irq_err_disable = usart_si32_irq_err_disable, + .irq_is_pending = usart_si32_irq_is_pending, + .irq_update = usart_si32_irq_update, + .irq_callback_set = usart_si32_irq_callback_set, +#endif /* CONFIG_UART_INTERRUPT_DRIVEN */ +}; + +static int usart_si32_init(const struct device *dev) +{ + const struct usart_si32_config *config = dev->config; + struct usart_si32_data *data = dev->data; + uint32_t apb_freq; + uint32_t baud_register_value; + int ret; + enum SI32_USART_A_PARITY_Enum parity = SI32_USART_A_PARITY_ODD; + bool parity_enabled; + + if (!device_is_ready(config->clock_dev)) { + return -ENODEV; + } + + ret = clock_control_get_rate(config->clock_dev, NULL, &apb_freq); + if (ret) { + return ret; + } + + switch (config->parity) { + case UART_CFG_PARITY_NONE: + parity_enabled = false; + break; + case UART_CFG_PARITY_ODD: + parity = SI32_USART_A_PARITY_ODD; + parity_enabled = true; + break; + case UART_CFG_PARITY_EVEN: + parity = SI32_USART_A_PARITY_EVEN; + parity_enabled = true; + break; + case UART_CFG_PARITY_MARK: + parity = SI32_USART_A_PARITY_SET; + parity_enabled = true; + break; + case UART_CFG_PARITY_SPACE: + parity = SI32_USART_A_PARITY_CLEAR; + parity_enabled = true; + break; + default: + return -ENOTSUP; + } + + if (config->usart == SI32_USART_0) { + SI32_CLKCTRL_A_enable_apb_to_modules_0(SI32_CLKCTRL_0, + SI32_CLKCTRL_A_APBCLKG0_USART0); + } else if (config->usart == SI32_USART_1) { + SI32_CLKCTRL_A_enable_apb_to_modules_0(SI32_CLKCTRL_0, + SI32_CLKCTRL_A_APBCLKG0_USART1); + } else { + return -ENOTSUP; + } + + baud_register_value = (apb_freq / (2 * data->baud_rate)) - 1; + + SI32_USART_A_exit_loopback_mode(config->usart); + + if (config->hw_flow_control) { + SI32_USART_A_enable_rts(config->usart); + SI32_USART_A_select_rts_deassert_on_byte_free(config->usart); + SI32_USART_A_disable_rts_inversion(config->usart); + + SI32_USART_A_enable_cts(config->usart); + SI32_USART_A_disable_cts_inversion(config->usart); + } + + /* Transmitter */ + if (parity_enabled) { + SI32_USART_A_select_tx_parity(config->usart, parity); + SI32_USART_A_enable_tx_parity_bit(config->usart); + } else { + SI32_USART_A_disable_tx_parity_bit(config->usart); + } + SI32_USART_A_select_tx_data_length(config->usart, SI32_USART_A_DATA_LENGTH_8_BITS); + SI32_USART_A_enable_tx_start_bit(config->usart); + SI32_USART_A_enable_tx_stop_bit(config->usart); + SI32_USART_A_select_tx_stop_bits(config->usart, SI32_USART_A_STOP_BITS_1_BIT); + SI32_USART_A_set_tx_baudrate(config->usart, (uint16_t)baud_register_value); + SI32_USART_A_select_tx_asynchronous_mode(config->usart); + SI32_USART_A_disable_tx_signal_inversion(config->usart); + SI32_USART_A_select_tx_fifo_threshold_for_request_to_1(config->usart); + SI32_USART_A_enable_tx(config->usart); + + /* Receiver */ + if (parity_enabled) { + SI32_USART_A_select_rx_parity(config->usart, parity); + SI32_USART_A_enable_rx_parity_bit(config->usart); + } else { + SI32_USART_A_disable_rx_parity_bit(config->usart); + } + SI32_USART_A_select_rx_data_length(config->usart, SI32_USART_A_DATA_LENGTH_8_BITS); + SI32_USART_A_enable_rx_start_bit(config->usart); + SI32_USART_A_enable_rx_stop_bit(config->usart); + SI32_USART_A_select_rx_stop_bits(config->usart, SI32_USART_A_STOP_BITS_1_BIT); + SI32_USART_A_set_rx_baudrate(config->usart, (uint16_t)baud_register_value); + SI32_USART_A_select_rx_asynchronous_mode(config->usart); + SI32_USART_A_disable_rx_signal_inversion(config->usart); + SI32_USART_A_select_rx_fifo_threshold_1(config->usart); + SI32_USART_A_enable_rx(config->usart); + + SI32_USART_A_flush_tx_fifo(config->usart); + SI32_USART_A_flush_rx_fifo(config->usart); + +#if defined(CONFIG_UART_INTERRUPT_DRIVEN) + config->irq_config_func(dev); +#endif + + return 0; +} + +#if defined(CONFIG_UART_INTERRUPT_DRIVEN) +#define SI32_USART_IRQ_HANDLER_DECL(index) \ + static void usart_si32_irq_config_func_##index(const struct device *dev); +#define SI32_USART_IRQ_HANDLER(index) \ + static void usart_si32_irq_config_func_##index(const struct device *dev) \ + { \ + IRQ_CONNECT(DT_INST_IRQN(index), DT_INST_IRQ(index, priority), \ + usart_si32_irq_handler, DEVICE_DT_INST_GET(index), 0); \ + irq_enable(DT_INST_IRQN(index)); \ + } +#else +#define SI32_USART_IRQ_HANDLER_DECL(index) /* Not used */ +#define SI32_USART_IRQ_HANDLER(index) /* Not used */ +#endif + +#if defined(CONFIG_UART_INTERRUPT_DRIVEN) +#define SI32_USART_IRQ_HANDLER_FUNC(index) .irq_config_func = usart_si32_irq_config_func_##index, +#else +#define SI32_USART_IRQ_HANDLER_FUNC(index) /* Not used */ +#endif + +#define SI32_USART_INIT(index) \ + SI32_USART_IRQ_HANDLER_DECL(index) \ + \ + static const struct usart_si32_config usart_si32_cfg_##index = { \ + .usart = (SI32_USART_A_Type *)DT_INST_REG_ADDR(index), \ + .hw_flow_control = DT_INST_PROP(index, hw_flow_control), \ + .parity = DT_INST_ENUM_IDX_OR(index, parity, UART_CFG_PARITY_NONE), \ + .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(index)), \ + SI32_USART_IRQ_HANDLER_FUNC(index)}; \ + \ + static struct usart_si32_data usart_si32_data_##index = { \ + .baud_rate = DT_INST_PROP(index, current_speed), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(index, &usart_si32_init, NULL, &usart_si32_data_##index, \ + &usart_si32_cfg_##index, PRE_KERNEL_1, CONFIG_SERIAL_INIT_PRIORITY, \ + &usart_si32_driver_api); \ + \ + SI32_USART_IRQ_HANDLER(index) + +DT_INST_FOREACH_STATUS_OKAY(SI32_USART_INIT) diff --git a/dts/arm/silabs/sim3u.dtsi b/dts/arm/silabs/sim3u.dtsi index cd9d66caf88..a9769ca8088 100644 --- a/dts/arm/silabs/sim3u.dtsi +++ b/dts/arm/silabs/sim3u.dtsi @@ -74,6 +74,22 @@ }; }; + usart0: usart@40000000 { + compatible = "silabs,si32-usart"; + reg = <0x40000000 0x1000>; + interrupts = <27 0>; + clocks = <&clk_apb>; + status = "disabled"; + }; + + usart1: usart@40001000 { + compatible = "silabs,si32-usart"; + reg = <0x40001000 0x1000>; + interrupts = <28 0>; + clocks = <&clk_apb>; + status = "disabled"; + }; + gpio0: gpio@4002a0a0 { compatible = "silabs,si32-gpio"; gpio-controller;