| /* |
| * Copyright (c) 2023 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| /** |
| * @brief UART driver for Intel FPGA UART Core IP |
| * Reference : Embedded Peripherals IP User Guide (22.3 and above): 55. |
| * Lightweight UART Core |
| */ |
| |
| #define DT_DRV_COMPAT intel_lw_uart |
| #include <zephyr/kernel.h> |
| #include <zephyr/drivers/uart.h> |
| |
| #include <zephyr/drivers/serial/uart_intel_lw.h> |
| |
| /* register offsets */ |
| #define INTEL_LW_UART_OFFSET (0x4) |
| |
| #define INTEL_LW_UART_RXDATA_REG_OFFSET (0 * INTEL_LW_UART_OFFSET) |
| #define INTEL_LW_UART_TXDATA_REG_OFFSET (1 * INTEL_LW_UART_OFFSET) |
| #define INTEL_LW_UART_STATUS_REG_OFFSET (2 * INTEL_LW_UART_OFFSET) |
| #define INTEL_LW_UART_CONTROL_REG_OFFSET (3 * INTEL_LW_UART_OFFSET) |
| #define INTEL_LW_UART_DIVISOR_REG_OFFSET (4 * INTEL_LW_UART_OFFSET) |
| #define INTEL_LW_UART_EOP_REG_OFFSET (5 * INTEL_LW_UART_OFFSET) |
| |
| /*status register mask */ |
| #define INTEL_LW_UART_STATUS_PE_MSK (0x1) |
| #define INTEL_LW_UART_STATUS_FE_MSK (0x2) |
| #define INTEL_LW_UART_STATUS_BRK_MSK (0x4) |
| #define INTEL_LW_UART_STATUS_ROE_MSK (0x8) |
| #define INTEL_LW_UART_STATUS_TOE_MSK (0x10) |
| #define INTEL_LW_UART_STATUS_TMT_MSK (0x20) |
| #define INTEL_LW_UART_STATUS_TRDY_MSK (0x40) |
| #define INTEL_LW_UART_STATUS_RRDY_MSK (0x80) |
| #define INTEL_LW_UART_STATUS_DCTS_MSK (0x400) |
| #define INTEL_LW_UART_STATUS_CTS_MSK (0x800) |
| #define INTEL_LW_UART_STATUS_E_MSK (0x100) |
| #define INTEL_LW_UART_STATUS_EOP_MSK (0x1000) |
| |
| /* control register mask */ |
| #define INTEL_LW_UART_CONTROL_TMT_MSK (0x20) |
| #define INTEL_LW_UART_CONTROL_TRDY_MSK (0x40) |
| #define INTEL_LW_UART_CONTROL_RRDY_MSK (0x80) |
| #define INTEL_LW_UART_CONTROL_E_MSK (0x100) |
| #define INTEL_LW_UART_CONTROL_TRBK_MSK (0x200) |
| #define INTEL_LW_UART_CONTROL_DCTS_MSK (0x400) |
| #define INTEL_LW_UART_CONTROL_RTS_MSK (0x800) |
| #define INTEL_LW_UART_CONTROL_EOP_MSK (0x1000) |
| |
| /* defined values */ |
| #define UART_INTEL_LW_NO_ERROR (0u) |
| #define INTEL_LW_UART_CLEAR_STATUS_VAL (0u) |
| #define INTEL_LW_UART_PENDING_MASK (INTEL_LW_UART_STATUS_RRDY_MSK | \ |
| INTEL_LW_UART_STATUS_TRDY_MSK | INTEL_LW_UART_STATUS_E_MSK \ |
| | INTEL_LW_UART_STATUS_EOP_MSK) |
| |
| /***********************/ |
| /* configuration flags */ |
| /* |
| * The value INTEL_LW_UART_FB is a value set in the devices flag field to |
| * indicate that the device has a fixed baud rate; i.e. if this flag is set |
| * software can not control the baud rate of the device. |
| */ |
| #define INTEL_LW_UART_FB 0x1 |
| |
| /* |
| * The value INTEL_LW_UART_FC is a value set in the device flag field to |
| * indicate the device is using flow control, i.e. the driver must |
| * throttle on transmit if the nCTS pin is low. |
| */ |
| #define INTEL_LW_UART_FC 0x2 |
| |
| /* end of configuration flags */ |
| /******************************/ |
| |
| /* device data */ |
| struct uart_intel_lw_device_data { |
| struct uart_config uart_cfg; /* stores uart config from device tree*/ |
| struct k_spinlock lock; |
| uint32_t status_act; /* stores value of status register. */ |
| #ifdef CONFIG_UART_INTERRUPT_DRIVEN |
| uart_irq_callback_user_data_t cb; /**< Callback function pointer */ |
| void *cb_data; /**< Callback function arg */ |
| #ifdef CONFIG_UART_INTEL_LW_EOP |
| uint8_t set_eop_cb; |
| uart_irq_callback_user_data_t cb_eop; /**< Callback function pointer */ |
| void *cb_data_eop; /**< Callback function arg */ |
| #endif /* CONFIG_UART_INTEL_LW_EOP */ |
| uint32_t control_val; /* stores value to set control register. */ |
| #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ |
| }; |
| |
| /* |
| * device config: |
| * stores data that cannot be changed during run time. |
| */ |
| struct uart_intel_lw_device_config { |
| mm_reg_t base; |
| uint32_t flags; /* refer to configuration flags */ |
| #ifdef CONFIG_UART_INTERRUPT_DRIVEN |
| uart_irq_config_func_t irq_config_func; |
| unsigned int irq_num; |
| #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ |
| }; |
| |
| #ifdef CONFIG_UART_INTERRUPT_DRIVEN |
| /** |
| * function prototypes |
| */ |
| static int uart_intel_lw_irq_update(const struct device *dev); |
| static int uart_intel_lw_irq_tx_ready(const struct device *dev); |
| static int uart_intel_lw_irq_rx_ready(const struct device *dev); |
| #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ |
| |
| /** |
| * @brief Poll the device for input. |
| * |
| * This is a non-blocking function. |
| * |
| * This driver supports interrupt driven api. |
| * Polling for data under normal operation, might cause unexpected behaviour. |
| * If users wish to poll in for data, please ensure that the data is not retrieved |
| * in interrupt. |
| * |
| * If UART_LINE_CTRL is enabled, please do not disable the hardware |
| * interrupt for this uart device, as the flow control is handled in uart_intel_lw_dcts_isr. |
| * |
| * @param dev UART device instance |
| * @param p_char Pointer to character |
| * |
| * @return 0 if a character arrived, -1 if input buffer is empty. |
| * -EINVAL if p_char is null pointer |
| */ |
| static int uart_intel_lw_poll_in(const struct device *dev, unsigned char *p_char) |
| { |
| const struct uart_intel_lw_device_config *config = dev->config; |
| struct uart_intel_lw_device_data *data = dev->data; |
| int ret_val = -1; |
| uint32_t status; |
| |
| /* generate fatal error if CONFIG_ASSERT is enabled. */ |
| __ASSERT(p_char != NULL, "p_char is null pointer!"); |
| |
| /* Stop, if p_char is null pointer */ |
| if (p_char == NULL) { |
| return -EINVAL; |
| } |
| |
| k_spinlock_key_t key = k_spin_lock(&data->lock); |
| |
| /* check if received character is ready.*/ |
| status = sys_read32(config->base + INTEL_LW_UART_STATUS_REG_OFFSET); |
| if (status & INTEL_LW_UART_STATUS_RRDY_MSK) { |
| /* got a character */ |
| *p_char = sys_read32(config->base + INTEL_LW_UART_RXDATA_REG_OFFSET); |
| ret_val = 0; |
| } |
| |
| k_spin_unlock(&data->lock, key); |
| |
| return ret_val; |
| } |
| |
| /** |
| * @brief Output a character in polled mode. |
| * |
| * This function will block until transmitter is ready. |
| * Then, a character will be transmitted. |
| * |
| * This driver supports interrupt driven api. |
| * Polling for data under normal operation, might cause unexpected behaviour. |
| * If users wish to poll out data, please ensure that the data is not transmitted |
| * in interrupt. |
| * |
| * If UART_LINE_CTRL is enabled and users wish to poll out only 1 character, |
| * please enable UART_INTEL_LW_AUTO_LINE_CTRL_POLL. Please note that this might |
| * be inefficient, in case of polling out several characters. Instead of using |
| * UART_INTEL_LW_AUTO_LINE_CTRL_POLL, please consider using the api: uart_drv_cmd |
| * with CMD_POLL_ASSERT_RTS before polling out. Then use CMD_POLL_DEASSERT_RTS to |
| * resume normal operation after all characters are polled out. |
| * |
| * Please do not set CMD_TRBK_EN, when polling out data. |
| * |
| * @param dev UART device instance |
| * @param c Character to send |
| */ |
| static void uart_intel_lw_poll_out(const struct device *dev, unsigned char c) |
| { |
| const struct uart_intel_lw_device_config *config = dev->config; |
| struct uart_intel_lw_device_data *data = dev->data; |
| uint32_t status; |
| bool tx_is_free = false; |
| bool poll_out_done = false; |
| k_spinlock_key_t key; |
| |
| /* wait until uart is free to transmit.*/ |
| do { |
| key = k_spin_lock(&data->lock); |
| |
| if (tx_is_free == false) { |
| status = sys_read32(config->base + INTEL_LW_UART_STATUS_REG_OFFSET); |
| |
| /* wait until there is space in fifo. */ |
| if (status & INTEL_LW_UART_STATUS_TRDY_MSK) { |
| #ifdef CONFIG_UART_INTEL_LW_AUTO_LINE_CTRL_POLL |
| data->control_val |= INTEL_LW_UART_CONTROL_RTS_MSK; |
| sys_write32(data->control_val, config->base |
| + INTEL_LW_UART_CONTROL_REG_OFFSET); |
| #endif |
| sys_write32(c, config->base + INTEL_LW_UART_TXDATA_REG_OFFSET); |
| tx_is_free = true; |
| } |
| } else { |
| status = sys_read32(config->base + INTEL_LW_UART_STATUS_REG_OFFSET); |
| |
| /* wait until fifo data is shifted out. */ |
| if (status & INTEL_LW_UART_STATUS_TMT_MSK) { |
| #ifdef CONFIG_UART_INTEL_LW_AUTO_LINE_CTRL_POLL |
| data->control_val &= ~INTEL_LW_UART_CONTROL_RTS_MSK; |
| sys_write32(data->control_val, config->base |
| + INTEL_LW_UART_CONTROL_REG_OFFSET); |
| #endif |
| poll_out_done = true; |
| } |
| } |
| |
| k_spin_unlock(&data->lock, key); |
| } while (!poll_out_done); |
| } |
| |
| /** |
| * @brief Initialise an instance of the driver |
| * |
| * This function initialise the interrupt configuration for the driver. |
| * |
| * @param dev UART device instance |
| * |
| * @return 0 to indicate success. |
| */ |
| static int uart_intel_lw_init(const struct device *dev) |
| { |
| #ifdef CONFIG_UART_INTERRUPT_DRIVEN |
| struct uart_intel_lw_device_data *data = dev->data; |
| const struct uart_intel_lw_device_config *config = dev->config; |
| /* clear status to ensure, that interrupts are not triggered due to old status. */ |
| sys_write32(INTEL_LW_UART_CLEAR_STATUS_VAL, config->base |
| + INTEL_LW_UART_STATUS_REG_OFFSET); |
| |
| /* |
| * Enable hardware interrupt. |
| * The corresponding csr from IP still needs to be set, |
| * so that the IP generates interrupt signal. |
| */ |
| config->irq_config_func(dev); |
| |
| #ifdef CONFIG_UART_LINE_CTRL |
| /* Enable DCTS interrupt. */ |
| data->control_val = INTEL_LW_UART_CONTROL_DCTS_MSK; |
| #endif /* CONFIG_UART_LINE_CTRL */ |
| |
| sys_write32(data->control_val, config->base + INTEL_LW_UART_CONTROL_REG_OFFSET); |
| |
| #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ |
| |
| return 0; |
| } |
| |
| /** |
| * @brief Check if an error was received |
| * If error is received, it will be mapped to uart_rx_stop_reason. |
| * This function should be called after irq_update. |
| * If interrupt driven API is not enabled, |
| * this function will read and clear the status register. |
| * |
| * @param dev UART device struct |
| * |
| * @return UART_ERROR_OVERRUN, UART_ERROR_PARITY, UART_ERROR_FRAMING, |
| * UART_BREAK if an error was detected, 0 otherwise. |
| */ |
| static int uart_intel_lw_err_check(const struct device *dev) |
| { |
| struct uart_intel_lw_device_data *data = dev->data; |
| int err = UART_INTEL_LW_NO_ERROR; |
| |
| #ifndef CONFIG_UART_INTERRUPT_DRIVEN |
| const struct uart_intel_lw_device_config *config = dev->config; |
| k_spinlock_key_t key = k_spin_lock(&data->lock); |
| |
| data->status_act = sys_read32(config->base + INTEL_LW_UART_STATUS_REG_OFFSET); |
| #endif |
| |
| if (data->status_act & INTEL_LW_UART_STATUS_E_MSK) { |
| if (data->status_act & INTEL_LW_UART_STATUS_PE_MSK) { |
| err |= UART_ERROR_PARITY; |
| } |
| |
| if (data->status_act & INTEL_LW_UART_STATUS_FE_MSK) { |
| err |= UART_ERROR_FRAMING; |
| } |
| |
| if (data->status_act & INTEL_LW_UART_STATUS_BRK_MSK) { |
| err |= UART_BREAK; |
| } |
| |
| if (data->status_act & INTEL_LW_UART_STATUS_ROE_MSK) { |
| err |= UART_ERROR_OVERRUN; |
| } |
| } |
| |
| #ifndef CONFIG_UART_INTERRUPT_DRIVEN |
| /* clear status */ |
| sys_write32(INTEL_LW_UART_CLEAR_STATUS_VAL, config->base |
| + INTEL_LW_UART_STATUS_REG_OFFSET); |
| k_spin_unlock(&data->lock, key); |
| #endif |
| |
| return err; |
| } |
| |
| #ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE |
| /*** |
| * @brief helper function to check, if the configuration is support. |
| * The only parameter that can be changed during runtime is the baudrate. |
| * |
| * @param cfg_stored The original configuration. |
| * @param cfg_in The input configuration. |
| * @return true if parameter other than baudrate remains the same. otherwise, false. |
| */ |
| static bool uart_intel_lw_check_configuration(const struct uart_config *cfg_stored, |
| const struct uart_config *cfg_in) |
| { |
| bool ret_val = false; |
| |
| if ((cfg_stored->parity == cfg_in->parity) |
| && (cfg_stored->stop_bits == cfg_in->stop_bits) |
| && (cfg_stored->data_bits == cfg_in->data_bits) |
| && (cfg_stored->flow_ctrl == cfg_in->flow_ctrl)) { |
| ret_val = true; |
| } |
| |
| return ret_val; |
| } |
| |
| /** |
| * @brief Set UART configuration using data from *cfg_in. |
| * |
| * @param dev UART Device struct |
| * @param cfg_in The input configuration. |
| * |
| * @return 0 if success, -ENOTSUP, if input from cfg_in is not configurable. |
| * -EINVAL if cfg_in is null pointer |
| */ |
| static int uart_intel_lw_configure(const struct device *dev, |
| const struct uart_config *cfg_in) |
| { |
| const struct uart_intel_lw_device_config *config = dev->config; |
| struct uart_intel_lw_device_data * const data = dev->data; |
| struct uart_config * const cfg_stored = &data->uart_cfg; |
| uint32_t divisor_val; |
| int ret_val; |
| |
| /* generate fatal error if CONFIG_ASSERT is enabled. */ |
| __ASSERT(cfg_in != NULL, "cfg_in is null pointer!"); |
| |
| /* Stop, if cfg_in is null pointer */ |
| if (cfg_in == NULL) { |
| return -EINVAL; |
| } |
| |
| /* check if configuration is supported. */ |
| if (uart_intel_lw_check_configuration(cfg_stored, cfg_in) |
| && !(config->flags & INTEL_LW_UART_FB)) { |
| /* parameter is valid, just return ok if baudrate is the same. */ |
| if (cfg_stored->baudrate != cfg_in->baudrate) { |
| /* calculate and set baudrate. */ |
| divisor_val = (CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC/cfg_in->baudrate) - 1; |
| sys_write32(divisor_val, config->base + INTEL_LW_UART_DIVISOR_REG_OFFSET); |
| |
| /* update stored data. */ |
| cfg_stored->baudrate = cfg_in->baudrate; |
| } |
| |
| ret_val = 0; |
| } else { |
| /* return not supported */ |
| ret_val = -ENOTSUP; |
| } |
| |
| return ret_val; |
| } |
| #endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */ |
| |
| /** |
| * @brief Get UART configuration and stores in *cfg_out. |
| * |
| * @param dev UART Device struct |
| * @param cfg_out The output configuration. |
| * |
| * @return 0 if success. |
| * -EINVAL if cfg_out is null pointer |
| */ |
| static int uart_intel_lw_config_get(const struct device *dev, |
| struct uart_config *cfg_out) |
| { |
| const struct uart_intel_lw_device_data *data = dev->data; |
| |
| /* generate fatal error if CONFIG_ASSERT is enabled. */ |
| __ASSERT(cfg_out != NULL, "cfg_out is null pointer!"); |
| |
| /* Stop, if cfg_out is null pointer */ |
| if (cfg_out == NULL) { |
| return -EINVAL; |
| } |
| |
| *cfg_out = data->uart_cfg; |
| return 0; |
| } |
| |
| #ifdef CONFIG_UART_INTERRUPT_DRIVEN |
| /** |
| * @brief Fill FIFO with data |
| * This function is expected to be called from UART interrupt handler (ISR), |
| * if uart_irq_tx_ready() returns true. This function does not block! |
| * |
| * @param dev UART device struct |
| * @param tx_data Data to transmit |
| * @param size Number of bytes to send |
| * |
| * @return Number of bytes sent |
| */ |
| static int uart_intel_lw_fifo_fill(const struct device *dev, |
| const uint8_t *tx_data, |
| int size) |
| { |
| const struct uart_intel_lw_device_config *config = dev->config; |
| struct uart_intel_lw_device_data *data = dev->data; |
| int ret_val = 0; |
| k_spinlock_key_t key; |
| |
| /* generate fatal error if CONFIG_ASSERT is enabled. */ |
| __ASSERT(tx_data != NULL, "tx_data is null pointer!"); |
| |
| /* Stop, if tx_data is null pointer */ |
| if (tx_data == NULL) { |
| return 0; |
| } |
| |
| /* Stop, if transmit break is set */ |
| if (data->control_val & INTEL_LW_UART_CONTROL_TRBK_MSK) { |
| return 0; |
| } |
| |
| do { |
| if (data->status_act & INTEL_LW_UART_STATUS_TRDY_MSK) { |
| key = k_spin_lock(&data->lock); |
| sys_write32(tx_data[ret_val++], config->base |
| + INTEL_LW_UART_TXDATA_REG_OFFSET); |
| data->status_act = sys_read32(config->base |
| + INTEL_LW_UART_STATUS_REG_OFFSET); |
| k_spin_unlock(&data->lock, key); |
| } else { |
| /* stop because tx fifo is full! */ |
| break; |
| } |
| } while ((size - ret_val) > 0); |
| |
| return ret_val; |
| } |
| |
| /** |
| * @brief Read data from FIFO |
| * This function is expected to be called from UART interrupt handler (ISR), |
| * if uart_irq_rx_ready() returns true. |
| * |
| * @param dev UART device struct |
| * @param rx_data Data container |
| * @param size Container size |
| * |
| * @return Number of bytes read |
| */ |
| static int uart_intel_lw_fifo_read(const struct device *dev, uint8_t *rx_data, |
| const int size) |
| { |
| const struct uart_intel_lw_device_config *config = dev->config; |
| struct uart_intel_lw_device_data *data = dev->data; |
| int ret_val = 0; |
| k_spinlock_key_t key; |
| |
| /* generate fatal error if CONFIG_ASSERT is enabled. */ |
| __ASSERT(rx_data != NULL, "rx_data is null pointer!"); |
| |
| /* Stop, if rx_data is null pointer */ |
| if (rx_data == NULL) { |
| return 0; |
| } |
| |
| do { |
| if (data->status_act & INTEL_LW_UART_STATUS_RRDY_MSK) { |
| key = k_spin_lock(&data->lock); |
| rx_data[ret_val++] = sys_read32(config->base + |
| INTEL_LW_UART_RXDATA_REG_OFFSET); |
| data->status_act = sys_read32(config->base |
| + INTEL_LW_UART_STATUS_REG_OFFSET); |
| |
| k_spin_unlock(&data->lock, key); |
| } else { |
| break; |
| } |
| } while ((size - ret_val) > 0); |
| |
| return ret_val; |
| } |
| |
| /** |
| * @brief Enable TX interrupt |
| * |
| * @param dev UART device struct |
| */ |
| static void uart_intel_lw_irq_tx_enable(const struct device *dev) |
| { |
| struct uart_intel_lw_device_data *data = dev->data; |
| const struct uart_intel_lw_device_config *config = dev->config; |
| |
| k_spinlock_key_t key = k_spin_lock(&data->lock); |
| |
| data->control_val |= INTEL_LW_UART_CONTROL_TRDY_MSK; |
| |
| #ifdef CONFIG_UART_LINE_CTRL |
| /* also enable RTS, if flow control is enabled. */ |
| data->control_val |= INTEL_LW_UART_CONTROL_RTS_MSK; |
| #endif |
| |
| sys_write32(data->control_val, config->base + INTEL_LW_UART_CONTROL_REG_OFFSET); |
| |
| k_spin_unlock(&data->lock, key); |
| } |
| |
| /** |
| * @brief Disable TX interrupt |
| * |
| * @param dev UART device struct |
| */ |
| static void uart_intel_lw_irq_tx_disable(const struct device *dev) |
| { |
| struct uart_intel_lw_device_data *data = dev->data; |
| const struct uart_intel_lw_device_config *config = dev->config; |
| |
| k_spinlock_key_t key = k_spin_lock(&data->lock); |
| |
| data->control_val &= ~INTEL_LW_UART_CONTROL_TRDY_MSK; |
| |
| #ifdef CONFIG_UART_LINE_CTRL |
| /* also disable RTS, if flow control is enabled. */ |
| data->control_val &= ~INTEL_LW_UART_CONTROL_RTS_MSK; |
| #endif |
| |
| sys_write32(data->control_val, config->base + INTEL_LW_UART_CONTROL_REG_OFFSET); |
| |
| k_spin_unlock(&data->lock, key); |
| } |
| |
| /** |
| * @brief Check if UART TX buffer can accept a new char. |
| * |
| * @param dev UART device struct |
| * |
| * @return 1 if TX interrupt is enabled and at least one char can be written to UART. |
| * 0 if device is not ready to write a new byte. |
| */ |
| static int uart_intel_lw_irq_tx_ready(const struct device *dev) |
| { |
| struct uart_intel_lw_device_data *data = dev->data; |
| int ret_val = 0; |
| |
| k_spinlock_key_t key = k_spin_lock(&data->lock); |
| |
| /* if TX interrupt is enabled */ |
| if (data->control_val & INTEL_LW_UART_CONTROL_TRDY_MSK) { |
| /* IP core does not have fifo. Wait until tx data is completely shifted. */ |
| if (data->status_act & INTEL_LW_UART_STATUS_TMT_MSK) { |
| ret_val = 1; |
| } |
| } |
| |
| #ifdef CONFIG_UART_LINE_CTRL |
| /* if flow control is enabled, set tx not ready, if CTS is low. */ |
| if ((data->status_act & INTEL_LW_UART_STATUS_CTS_MSK) == 0) { |
| ret_val = 0; |
| } |
| |
| #endif /* CONFIG_UART_LINE_CTRL */ |
| |
| k_spin_unlock(&data->lock, key); |
| |
| return ret_val; |
| } |
| |
| /** |
| * @brief Check if nothing remains to be transmitted |
| * |
| * @param dev UART device struct |
| * |
| * @return 1 if nothing remains to be transmitted, 0 otherwise |
| */ |
| static int uart_intel_lw_irq_tx_complete(const struct device *dev) |
| { |
| struct uart_intel_lw_device_data *data = dev->data; |
| int ret_val = 0; |
| |
| k_spinlock_key_t key = k_spin_lock(&data->lock); |
| |
| if (data->status_act & INTEL_LW_UART_STATUS_TMT_MSK) { |
| ret_val = 1; |
| } |
| |
| k_spin_unlock(&data->lock, key); |
| |
| return ret_val; |
| } |
| |
| /** |
| * @brief Enable RX interrupt in |
| * |
| * @param dev UART device struct |
| */ |
| static void uart_intel_lw_irq_rx_enable(const struct device *dev) |
| { |
| struct uart_intel_lw_device_data *data = dev->data; |
| const struct uart_intel_lw_device_config *config = dev->config; |
| |
| k_spinlock_key_t key = k_spin_lock(&data->lock); |
| |
| data->control_val |= INTEL_LW_UART_CONTROL_RRDY_MSK; |
| sys_write32(data->control_val, config->base + INTEL_LW_UART_CONTROL_REG_OFFSET); |
| |
| k_spin_unlock(&data->lock, key); |
| } |
| |
| /** |
| * @brief Disable RX interrupt |
| * |
| * @param dev UART device struct |
| */ |
| static void uart_intel_lw_irq_rx_disable(const struct device *dev) |
| { |
| struct uart_intel_lw_device_data *data = dev->data; |
| const struct uart_intel_lw_device_config *config = dev->config; |
| |
| k_spinlock_key_t key = k_spin_lock(&data->lock); |
| |
| data->control_val &= ~INTEL_LW_UART_CONTROL_RRDY_MSK; |
| sys_write32(data->control_val, config->base + INTEL_LW_UART_CONTROL_REG_OFFSET); |
| |
| k_spin_unlock(&data->lock, key); |
| } |
| |
| /** |
| * @brief Check if Rx IRQ has been raised |
| * |
| * @param dev UART device struct |
| * |
| * @return 1 if an IRQ is ready, 0 otherwise |
| */ |
| static int uart_intel_lw_irq_rx_ready(const struct device *dev) |
| { |
| struct uart_intel_lw_device_data *data = dev->data; |
| int ret_val = 0; |
| |
| k_spinlock_key_t key = k_spin_lock(&data->lock); |
| |
| /* if RX interrupt is enabled */ |
| if (data->control_val & INTEL_LW_UART_CONTROL_RRDY_MSK) { |
| /* check for space in rx data register */ |
| if (data->status_act & INTEL_LW_UART_STATUS_RRDY_MSK) { |
| ret_val = 1; |
| } |
| } |
| |
| k_spin_unlock(&data->lock, key); |
| |
| return ret_val; |
| } |
| |
| /** |
| * @brief This function will cache the status register. |
| * |
| * @param dev UART device struct |
| * |
| * @return 1 for success. |
| */ |
| static int uart_intel_lw_irq_update(const struct device *dev) |
| { |
| struct uart_intel_lw_device_data *data = dev->data; |
| const struct uart_intel_lw_device_config *config = dev->config; |
| |
| k_spinlock_key_t key = k_spin_lock(&data->lock); |
| |
| data->status_act = sys_read32(config->base + INTEL_LW_UART_STATUS_REG_OFFSET); |
| |
| k_spin_unlock(&data->lock, key); |
| |
| return 1; |
| } |
| |
| /** |
| * @brief Check if any IRQ is pending |
| * |
| * @param dev UART device struct |
| * |
| * @return 1 if an IRQ is pending, 0 otherwise |
| */ |
| static int uart_intel_lw_irq_is_pending(const struct device *dev) |
| { |
| struct uart_intel_lw_device_data *data = dev->data; |
| int ret_val = 0; |
| |
| k_spinlock_key_t key = k_spin_lock(&data->lock); |
| |
| if (data->status_act & data->control_val & INTEL_LW_UART_PENDING_MASK) { |
| ret_val = 1; |
| } |
| |
| k_spin_unlock(&data->lock, key); |
| |
| return ret_val; |
| } |
| |
| /** |
| * @brief Set the callback function pointer for IRQ. |
| * |
| * @param dev UART device struct |
| * @param cb Callback function pointer. |
| * @param cb_data Data to pass to callback function. |
| */ |
| static void uart_intel_lw_irq_callback_set(const struct device *dev, |
| uart_irq_callback_user_data_t cb, |
| void *cb_data) |
| { |
| struct uart_intel_lw_device_data *data = dev->data; |
| |
| k_spinlock_key_t key = k_spin_lock(&data->lock); |
| |
| #ifdef CONFIG_UART_INTEL_LW_EOP |
| if (data->set_eop_cb) { |
| data->cb_eop = cb; |
| data->cb_data_eop = cb_data; |
| data->set_eop_cb = 0; |
| } else { |
| data->cb = cb; |
| data->cb_data = cb_data; |
| } |
| #else |
| data->cb = cb; |
| data->cb_data = cb_data; |
| #endif /* CONFIG_UART_INTEL_LW_EOP */ |
| |
| k_spin_unlock(&data->lock, key); |
| } |
| |
| #ifdef CONFIG_UART_LINE_CTRL |
| /** |
| * @brief DCTS Interrupt service routine. |
| * |
| * Handles assertion and deassettion of CTS/RTS stignal |
| * |
| * @param dev Pointer to UART device struct |
| */ |
| static void uart_intel_lw_dcts_isr(const struct device *dev) |
| { |
| struct uart_intel_lw_device_data *data = dev->data; |
| const struct uart_intel_lw_device_config *config = dev->config; |
| |
| k_spinlock_key_t key = k_spin_lock(&data->lock); |
| |
| /* Assume that user follows zephyr requirement and update status in their call back. */ |
| if (data->status_act & INTEL_LW_UART_STATUS_CTS_MSK) { |
| |
| /* Assert RTS to inform other UART. */ |
| data->control_val |= INTEL_LW_UART_CONTROL_RTS_MSK; |
| sys_write32(data->control_val, config->base |
| + INTEL_LW_UART_CONTROL_REG_OFFSET); |
| } else { |
| /* other UART deasserts RTS */ |
| if (data->status_act & INTEL_LW_UART_STATUS_TMT_MSK) { |
| /* only deasserts if not transmitting. */ |
| data->control_val &= ~INTEL_LW_UART_CONTROL_RTS_MSK; |
| sys_write32(data->control_val, config->base |
| + INTEL_LW_UART_CONTROL_REG_OFFSET); |
| } |
| } |
| |
| k_spin_unlock(&data->lock, key); |
| } |
| #endif /* CONFIG_UART_LINE_CTRL */ |
| |
| /** |
| * @brief Interrupt service routine. |
| * |
| * This simply calls the callback function, if one exists. |
| * |
| * @param dev Pointer to UART device struct |
| * |
| */ |
| static void uart_intel_lw_isr(const struct device *dev) |
| { |
| struct uart_intel_lw_device_data *data = dev->data; |
| const struct uart_intel_lw_device_config *config = dev->config; |
| |
| uart_irq_callback_user_data_t callback = data->cb; |
| |
| if (callback) { |
| callback(dev, data->cb_data); |
| } |
| |
| /* Post ISR */ |
| #if CONFIG_UART_INTEL_LW_EOP |
| data->status_act = sys_read32(config->base + INTEL_LW_UART_STATUS_REG_OFFSET); |
| if (data->status_act & INTEL_LW_UART_STATUS_EOP_MSK) { |
| callback = data->cb_eop; |
| if (callback) { |
| callback(dev, data->cb_data_eop); |
| } |
| } |
| #endif /* CONFIG_UART_INTEL_LW_EOP */ |
| |
| #ifdef CONFIG_UART_LINE_CTRL |
| data->status_act = sys_read32(config->base + INTEL_LW_UART_STATUS_REG_OFFSET); |
| /* handles RTS/CTS signal */ |
| if (data->status_act & INTEL_LW_UART_STATUS_DCTS_MSK) { |
| uart_intel_lw_dcts_isr(dev); |
| } |
| #endif |
| |
| /* clear status after all interrupts are handled. */ |
| sys_write32(INTEL_LW_UART_CLEAR_STATUS_VAL, config->base |
| + INTEL_LW_UART_STATUS_REG_OFFSET); |
| } |
| |
| #ifdef CONFIG_UART_DRV_CMD |
| /** |
| * @brief Send extra command to driver |
| * |
| * @param dev UART device struct |
| * @param cmd Command to driver |
| * @param p Parameter to the command |
| * |
| * @return 0 if successful, failed otherwise |
| */ |
| static int uart_intel_lw_drv_cmd(const struct device *dev, uint32_t cmd, |
| uint32_t p) |
| { |
| struct uart_intel_lw_device_data *data = dev->data; |
| const struct uart_intel_lw_device_config *config = dev->config; |
| |
| int ret_val = -ENOTSUP; |
| k_spinlock_key_t key = k_spin_lock(&data->lock); |
| |
| switch (cmd) { |
| #if CONFIG_UART_INTEL_LW_EOP |
| case CMD_ENABLE_EOP: |
| /* enable EOP interrupt */ |
| data->control_val |= INTEL_LW_UART_CONTROL_EOP_MSK; |
| sys_write32(data->control_val, config->base |
| + INTEL_LW_UART_CONTROL_REG_OFFSET); |
| |
| /* set EOP character */ |
| sys_write32((uint8_t) p, config->base + INTEL_LW_UART_EOP_REG_OFFSET); |
| |
| /* after this, user needs to call uart_irq_callback_set |
| * to set data->cb_eop and data->cb_data_eop! |
| */ |
| data->set_eop_cb = 1; |
| ret_val = 0; |
| break; |
| |
| case CMD_DISABLE_EOP: |
| /* disable EOP interrupt */ |
| data->control_val &= ~INTEL_LW_UART_CONTROL_EOP_MSK; |
| sys_write32(data->control_val, config->base |
| + INTEL_LW_UART_CONTROL_REG_OFFSET); |
| |
| /* clear call back */ |
| data->cb_eop = NULL; |
| data->cb_data_eop = NULL; |
| ret_val = 0; |
| break; |
| #endif /* CONFIG_UART_INTEL_LW_EOP */ |
| |
| case CMD_TRBK_EN: |
| /* enable transmit break */ |
| data->control_val |= INTEL_LW_UART_CONTROL_TRBK_MSK; |
| sys_write32(data->control_val, config->base |
| + INTEL_LW_UART_CONTROL_REG_OFFSET); |
| ret_val = 0; |
| break; |
| |
| case CMD_TRBK_DIS: |
| /* disable transmit break */ |
| data->control_val &= ~INTEL_LW_UART_CONTROL_TRBK_MSK; |
| sys_write32(data->control_val, config->base |
| + INTEL_LW_UART_CONTROL_REG_OFFSET); |
| ret_val = 0; |
| break; |
| |
| case CMD_POLL_ASSERT_RTS: |
| /* assert RTS */ |
| data->control_val |= INTEL_LW_UART_CONTROL_RTS_MSK; |
| sys_write32(data->control_val, config->base |
| + INTEL_LW_UART_CONTROL_REG_OFFSET); |
| ret_val = 0; |
| break; |
| |
| case CMD_POLL_DEASSERT_RTS: |
| /* deassert RTS */ |
| data->control_val &= ~INTEL_LW_UART_CONTROL_RTS_MSK; |
| sys_write32(data->control_val, config->base |
| + INTEL_LW_UART_CONTROL_REG_OFFSET); |
| ret_val = 0; |
| break; |
| |
| default: |
| ret_val = -ENOTSUP; |
| break; |
| }; |
| |
| k_spin_unlock(&data->lock, key); |
| |
| return ret_val; |
| } |
| |
| #endif /* CONFIG_UART_DRV_CMD */ |
| |
| #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ |
| |
| static const struct uart_driver_api uart_intel_lw_driver_api = { |
| .poll_in = uart_intel_lw_poll_in, |
| .poll_out = uart_intel_lw_poll_out, |
| .err_check = uart_intel_lw_err_check, |
| #ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE |
| .configure = uart_intel_lw_configure, |
| #endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */ |
| .config_get = uart_intel_lw_config_get, |
| |
| #ifdef CONFIG_UART_INTERRUPT_DRIVEN |
| .fifo_fill = uart_intel_lw_fifo_fill, |
| .fifo_read = uart_intel_lw_fifo_read, |
| .irq_tx_enable = uart_intel_lw_irq_tx_enable, |
| .irq_tx_disable = uart_intel_lw_irq_tx_disable, |
| .irq_tx_ready = uart_intel_lw_irq_tx_ready, |
| .irq_tx_complete = uart_intel_lw_irq_tx_complete, |
| .irq_rx_enable = uart_intel_lw_irq_rx_enable, |
| .irq_rx_disable = uart_intel_lw_irq_rx_disable, |
| .irq_rx_ready = uart_intel_lw_irq_rx_ready, |
| .irq_is_pending = uart_intel_lw_irq_is_pending, |
| .irq_update = uart_intel_lw_irq_update, |
| .irq_callback_set = uart_intel_lw_irq_callback_set, |
| #endif |
| |
| #ifdef CONFIG_UART_DRV_CMD |
| .drv_cmd = uart_intel_lw_drv_cmd, |
| #endif |
| }; |
| |
| #ifdef CONFIG_UART_INTERRUPT_DRIVEN |
| |
| #define UART_INTEL_LW_IRQ_CONFIG_FUNC(n) \ |
| static void uart_intel_lw_irq_config_func_##n(const struct device *dev) \ |
| { \ |
| IRQ_CONNECT(DT_INST_IRQN(n), \ |
| DT_INST_IRQ(n, priority), \ |
| uart_intel_lw_isr, \ |
| DEVICE_DT_INST_GET(n), 0); \ |
| \ |
| irq_enable(DT_INST_IRQN(n)); \ |
| } |
| |
| #define UART_INTEL_LW_IRQ_CONFIG_INIT(n) \ |
| .irq_config_func = uart_intel_lw_irq_config_func_##n, \ |
| .irq_num = DT_INST_IRQN(n), |
| |
| #else |
| |
| #define UART_INTEL_LW_IRQ_CONFIG_FUNC(n) |
| #define UART_INTEL_LW_IRQ_CONFIG_INIT(n) |
| |
| #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ |
| |
| #define UART_INTEL_LW_DEVICE_INIT(n) \ |
| UART_INTEL_LW_IRQ_CONFIG_FUNC(n) \ |
| static struct uart_intel_lw_device_data uart_intel_lw_dev_data_##n = { \ |
| .uart_cfg = \ |
| { \ |
| .baudrate = DT_INST_PROP(n, current_speed), \ |
| .parity = DT_INST_ENUM_IDX_OR(n, parity, \ |
| UART_CFG_PARITY_NONE), \ |
| .stop_bits = DT_INST_ENUM_IDX_OR(n, stop_bits, \ |
| UART_CFG_STOP_BITS_1), \ |
| .data_bits = DT_INST_ENUM_IDX_OR(n, data_bits, \ |
| UART_CFG_DATA_BITS_8), \ |
| .flow_ctrl = DT_INST_PROP(n, hw_flow_control) ? \ |
| UART_CFG_FLOW_CTRL_RTS_CTS : \ |
| UART_CFG_FLOW_CTRL_NONE, \ |
| }, \ |
| }; \ |
| \ |
| static const struct uart_intel_lw_device_config uart_intel_lw_dev_cfg_##n = { \ |
| .base = DT_INST_REG_ADDR(n), \ |
| .flags = ((DT_INST_PROP(n, fixed_baudrate)?INTEL_LW_UART_FB:0) \ |
| |(DT_INST_PROP(n, hw_flow_control)?INTEL_LW_UART_FC:0)),\ |
| UART_INTEL_LW_IRQ_CONFIG_INIT(n) \ |
| }; \ |
| \ |
| DEVICE_DT_INST_DEFINE(n, \ |
| uart_intel_lw_init, \ |
| NULL, \ |
| &uart_intel_lw_dev_data_##n, \ |
| &uart_intel_lw_dev_cfg_##n, \ |
| PRE_KERNEL_1, \ |
| CONFIG_SERIAL_INIT_PRIORITY, \ |
| &uart_intel_lw_driver_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(UART_INTEL_LW_DEVICE_INIT) |