/* * Copyright (c) 2017-2023 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ /** * @brief UART driver for Intel FPGA UART Core IP * Reference : Embedded Peripherals IP User Guide : 12. JTAG UART Core * */ #include #include #include #include #include #define DT_DRV_COMPAT altr_jtag_uart #define UART_ALTERA_JTAG_DATA_OFFSET 0x00 /* DATA : Register offset */ #define UART_ALTERA_JTAG_CTRL_OFFSET 0x04 /* CTRL : Register offset */ #define UART_IE_TX (1 << 1) /* CTRL : TX Interrupt Enable */ #define UART_IE_RX (1 << 0) /* CTRL : RX Interrupt Enable */ #define UART_DATA_MASK 0xFF /* DATA : Data Mask */ #define UART_WFIFO_MASK 0xFFFF0000 /* CTRL : Transmit FIFO Mask */ #define UART_WFIFO_OFST (16) #define ALTERA_AVALON_JTAG_UART_DATA_DATA_OFST (0) #define ALTERA_AVALON_JTAG_UART_DATA_RVALID_MSK (0x00008000) #define ALTERA_AVALON_JTAG_UART_CONTROL_RI_MSK (0x00000100) #define ALTERA_AVALON_JTAG_UART_CONTROL_WI_MSK (0x00000200) #ifdef CONFIG_UART_ALTERA_JTAG_HAL #include "altera_avalon_jtag_uart.h" #include "altera_avalon_jtag_uart_regs.h" extern int altera_avalon_jtag_uart_read(altera_avalon_jtag_uart_state *sp, char *buffer, int space, int flags); extern int altera_avalon_jtag_uart_write(altera_avalon_jtag_uart_state *sp, const char *ptr, int count, int flags); #else /* device data */ struct uart_altera_jtag_device_data { struct k_spinlock lock; #ifdef CONFIG_UART_INTERRUPT_DRIVEN uart_irq_callback_user_data_t cb; /* Callback function pointer */ void *cb_data; /* Callback function arg */ #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ }; /* device config */ struct uart_altera_jtag_device_config { mm_reg_t base; #ifdef CONFIG_UART_INTERRUPT_DRIVEN uart_irq_config_func_t irq_config_func; unsigned int irq_num; uint16_t write_fifo_depth; #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ }; #endif /* CONFIG_UART_ALTERA_JTAG_HAL */ #ifndef CONFIG_UART_ALTERA_JTAG_HAL /** * @brief Poll the device for input. * * @param dev UART device instance * @param c Pointer to character * * @return 0 if a character arrived, -1 otherwise. * -EINVAL if c is null pointer. */ static int uart_altera_jtag_poll_in(const struct device *dev, unsigned char *c) { int ret = -1; const struct uart_altera_jtag_device_config *config = dev->config; struct uart_altera_jtag_device_data *data = dev->data; unsigned int input_data; /* generate fatal error if CONFIG_ASSERT is enabled. */ __ASSERT(c != NULL, "c is null pointer!"); /* Stop, if c is null pointer */ if (c == NULL) { return -EINVAL; } k_spinlock_key_t key = k_spin_lock(&data->lock); input_data = sys_read32(config->base + UART_ALTERA_JTAG_DATA_OFFSET); /* check if data is valid. */ if (input_data & ALTERA_AVALON_JTAG_UART_DATA_RVALID_MSK) { *c = (input_data & UART_DATA_MASK) >> ALTERA_AVALON_JTAG_UART_DATA_DATA_OFST; ret = 0; } k_spin_unlock(&data->lock, key); return ret; } #endif /* CONFIG_UART_ALTERA_JTAG_HAL */ /** * @brief Output a character in polled mode. * * This routine checks if the transmitter is full. * When the transmitter is not full, it writes a character to the data register. * It waits and blocks the calling thread, otherwise. This function is a blocking call. * * @param dev UART device instance * @param c Character to send */ static void uart_altera_jtag_poll_out(const struct device *dev, unsigned char c) { #ifdef CONFIG_UART_ALTERA_JTAG_HAL altera_avalon_jtag_uart_state ustate; ustate.base = JTAG_UART_0_BASE; altera_avalon_jtag_uart_write(&ustate, &c, 1, 0); #else const struct uart_altera_jtag_device_config *config = dev->config; struct uart_altera_jtag_device_data *data = dev->data; k_spinlock_key_t key = k_spin_lock(&data->lock); /* While TX FIFO full */ while (!(sys_read32(config->base + UART_ALTERA_JTAG_CTRL_OFFSET) & UART_WFIFO_MASK)) { } sys_write8(c, config->base + UART_ALTERA_JTAG_DATA_OFFSET); k_spin_unlock(&data->lock, key); #endif /* CONFIG_UART_ALTERA_JTAG_HAL */ } /** * @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_altera_jtag_init(const struct device *dev) { /* * Work around to clear interrupt enable bits * as it is not being done by HAL driver explicitly. */ #ifdef CONFIG_UART_ALTERA_JTAG_HAL IOWR_ALTERA_AVALON_JTAG_UART_CONTROL(JTAG_UART_0_BASE, 0); #else const struct uart_altera_jtag_device_config *config = dev->config; uint32_t ctrl_val = sys_read32(config->base + UART_ALTERA_JTAG_CTRL_OFFSET); #ifdef CONFIG_UART_INTERRUPT_DRIVEN /* * 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); #endif /* Disable the tx and rx interrupt signals from JTAG core IP. */ ctrl_val &= ~(UART_IE_TX | UART_IE_RX); sys_write32(ctrl_val, config->base + UART_ALTERA_JTAG_CTRL_OFFSET); #endif /* CONFIG_UART_ALTERA_JTAG_HAL */ return 0; } /* * Functions for Interrupt driven API */ #if defined(CONFIG_UART_INTERRUPT_DRIVEN) && !defined(CONFIG_UART_ALTERA_JTAG_HAL) /** * @brief Fill FIFO with data * This function is expected to be called from UART interrupt handler (ISR), * if uart_irq_tx_ready() returns true. * * @param dev UART device instance * @param tx_data Data to transmit * @param size Number of bytes to send * * @return Number of bytes sent */ static int uart_altera_jtag_fifo_fill(const struct device *dev, const uint8_t *tx_data, int size) { const struct uart_altera_jtag_device_config *config = dev->config; struct uart_altera_jtag_device_data *data = dev->data; uint32_t ctrl_val; uint32_t space = 0; int i; /* generate fatal error if CONFIG_ASSERT is enabled. */ __ASSERT(tx_data != NULL, "tx buffer is null pointer!"); /* Stop, if buffer is null pointer */ if (tx_data == NULL) { return 0; } k_spinlock_key_t key = k_spin_lock(&data->lock); ctrl_val = sys_read32(config->base + UART_ALTERA_JTAG_CTRL_OFFSET); space = (ctrl_val & UART_WFIFO_MASK) >> UART_WFIFO_OFST; /* guard for tx data overflow: * make sure that driver is not sending more than current available space. */ for (i = 0; (i < size) && (i < space); i++) { sys_write8(tx_data[i], config->base + UART_ALTERA_JTAG_DATA_OFFSET); } k_spin_unlock(&data->lock, key); return i; } /** * @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 instance * @param rx_data Data container * @param size Container size * * @return Number of bytes read */ static int uart_altera_jtag_fifo_read(const struct device *dev, uint8_t *rx_data, const int size) { const struct uart_altera_jtag_device_config *config = dev->config; struct uart_altera_jtag_device_data *data = dev->data; int i; unsigned int input_data; /* generate fatal error if CONFIG_ASSERT is enabled. */ __ASSERT(rx_data != NULL, "Rx buffer is null pointer!"); /* Stop, if buffer is null pointer */ if (rx_data == NULL) { return 0; } k_spinlock_key_t key = k_spin_lock(&data->lock); for (i = 0; i < size; i++) { input_data = sys_read32(config->base + UART_ALTERA_JTAG_DATA_OFFSET); if (input_data & ALTERA_AVALON_JTAG_UART_DATA_RVALID_MSK) { rx_data[i] = (input_data & UART_DATA_MASK) >> ALTERA_AVALON_JTAG_UART_DATA_DATA_OFST; } else { /* break upon invalid data or no more data */ break; } } k_spin_unlock(&data->lock, key); return i; } /** * @brief Enable TX interrupt in IER * * @param dev UART device instance */ static void uart_altera_jtag_irq_tx_enable(const struct device *dev) { struct uart_altera_jtag_device_data *data = dev->data; const struct uart_altera_jtag_device_config *config = dev->config; uint32_t ctrl_val = sys_read32(config->base + UART_ALTERA_JTAG_CTRL_OFFSET); k_spinlock_key_t key = k_spin_lock(&data->lock); ctrl_val |= UART_IE_TX; sys_write32(ctrl_val, config->base + UART_ALTERA_JTAG_CTRL_OFFSET); k_spin_unlock(&data->lock, key); } /** * @brief Disable TX interrupt in IER * * @param dev UART device instance */ static void uart_altera_jtag_irq_tx_disable(const struct device *dev) { struct uart_altera_jtag_device_data *data = dev->data; const struct uart_altera_jtag_device_config *config = dev->config; uint32_t ctrl_val = sys_read32(config->base + UART_ALTERA_JTAG_CTRL_OFFSET); k_spinlock_key_t key = k_spin_lock(&data->lock); ctrl_val &= ~UART_IE_TX; sys_write32(ctrl_val, config->base + UART_ALTERA_JTAG_CTRL_OFFSET); k_spin_unlock(&data->lock, key); } /** * @brief Check if UART TX buffer can accept a new char. * * @param dev UART device instance * * @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_altera_jtag_irq_tx_ready(const struct device *dev) { struct uart_altera_jtag_device_data *data = dev->data; const struct uart_altera_jtag_device_config *config = dev->config; uint32_t ctrl_val = sys_read32(config->base + UART_ALTERA_JTAG_CTRL_OFFSET); int ret = 0; uint32_t space = 0; k_spinlock_key_t key = k_spin_lock(&data->lock); /* if TX interrupt is enabled */ if (ctrl_val & ALTERA_AVALON_JTAG_UART_CONTROL_WI_MSK) { /* check for space in tx fifo */ space = (ctrl_val & UART_WFIFO_MASK) >> UART_WFIFO_OFST; if (space) { ret = 1; } } k_spin_unlock(&data->lock, key); return ret; } /** * @brief Check if nothing remains to be transmitted * * @param dev UART device instance * * @return 1 if nothing remains to be transmitted, 0 otherwise */ static int uart_altera_jtag_irq_tx_complete(const struct device *dev) { struct uart_altera_jtag_device_data *data = dev->data; const struct uart_altera_jtag_device_config *config = dev->config; uint32_t ctrl_val = sys_read32(config->base + UART_ALTERA_JTAG_CTRL_OFFSET); int ret = 0; uint32_t space = 0; k_spinlock_key_t key = k_spin_lock(&data->lock); /* note: This is checked indirectly via the space in tx fifo. */ space = (ctrl_val & UART_WFIFO_MASK) >> UART_WFIFO_OFST; if (space == config->write_fifo_depth) { ret = 1; } k_spin_unlock(&data->lock, key); return ret; } /** * @brief Enable RX interrupt in IER * * @param dev UART device instance */ static void uart_altera_jtag_irq_rx_enable(const struct device *dev) { struct uart_altera_jtag_device_data *data = dev->data; const struct uart_altera_jtag_device_config *config = dev->config; uint32_t ctrl_val = sys_read32(config->base + UART_ALTERA_JTAG_CTRL_OFFSET); k_spinlock_key_t key = k_spin_lock(&data->lock); ctrl_val |= UART_IE_RX; sys_write32(ctrl_val, config->base + UART_ALTERA_JTAG_CTRL_OFFSET); k_spin_unlock(&data->lock, key); } /** * @brief Disable RX interrupt in IER * * @param dev UART device instance */ static void uart_altera_jtag_irq_rx_disable(const struct device *dev) { struct uart_altera_jtag_device_data *data = dev->data; const struct uart_altera_jtag_device_config *config = dev->config; uint32_t ctrl_val = sys_read32(config->base + UART_ALTERA_JTAG_CTRL_OFFSET); k_spinlock_key_t key = k_spin_lock(&data->lock); ctrl_val &= ~UART_IE_RX; sys_write32(ctrl_val, config->base + UART_ALTERA_JTAG_CTRL_OFFSET); k_spin_unlock(&data->lock, key); } /** * @brief Check if Rx IRQ has been raised * * @param dev UART device instance * * @return 1 if an IRQ is ready, 0 otherwise */ static int uart_altera_jtag_irq_rx_ready(const struct device *dev) { struct uart_altera_jtag_device_data *data = dev->data; const struct uart_altera_jtag_device_config *config = dev->config; uint32_t ctrl_val = sys_read32(config->base + UART_ALTERA_JTAG_CTRL_OFFSET); int ret = 0; k_spinlock_key_t key = k_spin_lock(&data->lock); if (ctrl_val & ALTERA_AVALON_JTAG_UART_CONTROL_RI_MSK) { ret = 1; } k_spin_unlock(&data->lock, key); return ret; } /** * @brief Update cached contents of IIR * * @param dev UART device instance * * @return Always 1 */ static int uart_altera_jtag_irq_update(const struct device *dev) { return 1; } /** * @brief Check if any IRQ is pending * * @param dev UART device instance * * @return 1 if an IRQ is pending, 0 otherwise */ static int uart_altera_jtag_irq_is_pending(const struct device *dev) { struct uart_altera_jtag_device_data *data = dev->data; k_spinlock_key_t key = k_spin_lock(&data->lock); const struct uart_altera_jtag_device_config *config = dev->config; uint32_t ctrl_val = sys_read32(config->base + UART_ALTERA_JTAG_CTRL_OFFSET); int ret = 0; if (ctrl_val & (ALTERA_AVALON_JTAG_UART_CONTROL_RI_MSK|ALTERA_AVALON_JTAG_UART_CONTROL_WI_MSK)) { ret = 1; } k_spin_unlock(&data->lock, key); return ret; } /** * @brief Set the callback function pointer for IRQ. * * @param dev UART device instance * @param cb Callback function pointer. * @param cb_data Data to pass to callback function. */ static void uart_altera_jtag_irq_callback_set(const struct device *dev, uart_irq_callback_user_data_t cb, void *cb_data) { struct uart_altera_jtag_device_data *data = dev->data; /* generate fatal error if CONFIG_ASSERT is enabled. */ __ASSERT(cb != NULL, "uart_irq_callback_user_data_t cb is null pointer!"); k_spinlock_key_t key = k_spin_lock(&data->lock); data->cb = cb; data->cb_data = cb_data; k_spin_unlock(&data->lock, key); } /** * @brief Interrupt service routine. * * This simply calls the callback function, if one exists. * * @param dev Pointer to UART device struct */ static void uart_altera_jtag_isr(const struct device *dev) { struct uart_altera_jtag_device_data *data = dev->data; uart_irq_callback_user_data_t callback = data->cb; if (callback) { callback(dev, data->cb_data); } } #endif /* CONFIG_UART_INTERRUPT_DRIVEN && !CONFIG_UART_ALTERA_JTAG_HAL */ static const struct uart_driver_api uart_altera_jtag_driver_api = { #ifndef CONFIG_UART_ALTERA_JTAG_HAL .poll_in = uart_altera_jtag_poll_in, #endif /* CONFIG_UART_ALTERA_JTAG_HAL */ .poll_out = uart_altera_jtag_poll_out, .err_check = NULL, #if defined(CONFIG_UART_INTERRUPT_DRIVEN) && !defined(CONFIG_UART_ALTERA_JTAG_HAL) .fifo_fill = uart_altera_jtag_fifo_fill, .fifo_read = uart_altera_jtag_fifo_read, .irq_tx_enable = uart_altera_jtag_irq_tx_enable, .irq_tx_disable = uart_altera_jtag_irq_tx_disable, .irq_tx_ready = uart_altera_jtag_irq_tx_ready, .irq_tx_complete = uart_altera_jtag_irq_tx_complete, .irq_rx_enable = uart_altera_jtag_irq_rx_enable, .irq_rx_disable = uart_altera_jtag_irq_rx_disable, .irq_rx_ready = uart_altera_jtag_irq_rx_ready, .irq_is_pending = uart_altera_jtag_irq_is_pending, .irq_update = uart_altera_jtag_irq_update, .irq_callback_set = uart_altera_jtag_irq_callback_set, #endif /* CONFIG_UART_INTERRUPT_DRIVEN && !CONFIG_UART_ALTERA_JTAG_HAL */ }; #ifdef CONFIG_UART_ALTERA_JTAG_HAL #define UART_ALTERA_JTAG_DEVICE_INIT(n) \ DEVICE_DT_INST_DEFINE(n, uart_altera_jtag_init, NULL, NULL, NULL, PRE_KERNEL_1, \ CONFIG_SERIAL_INIT_PRIORITY, \ &uart_altera_jtag_driver_api); #else #ifdef CONFIG_UART_INTERRUPT_DRIVEN #define UART_ALTERA_JTAG_CONFIG_FUNC(n) \ static void uart_altera_jtag_irq_config_func_##n(const struct device *dev) \ { \ IRQ_CONNECT(DT_INST_IRQN(n), \ DT_INST_IRQ(n, priority), \ uart_altera_jtag_isr, \ DEVICE_DT_INST_GET(n), 0); \ \ irq_enable(DT_INST_IRQN(n)); \ } #define UART_ALTERA_JTAG_CONFIG_INIT(n) \ .irq_config_func = uart_altera_jtag_irq_config_func_##n, \ .irq_num = DT_INST_IRQN(n), \ .write_fifo_depth = DT_INST_PROP_OR(n, write_fifo_depth, 0),\ #else #define UART_ALTERA_JTAG_CONFIG_FUNC(n) #define UART_ALTERA_JTAG_CONFIG_INIT(n) #define UART_ALTERA_JTAG_DATA_INIT(n) #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ #define UART_ALTERA_JTAG_DEVICE_INIT(n) \ UART_ALTERA_JTAG_CONFIG_FUNC(n) \ static struct uart_altera_jtag_device_data uart_altera_jtag_device_data_##n = { \ }; \ \ static const struct uart_altera_jtag_device_config uart_altera_jtag_dev_cfg_##n = { \ .base = DT_INST_REG_ADDR(n), \ UART_ALTERA_JTAG_CONFIG_INIT(n) \ }; \ DEVICE_DT_INST_DEFINE(n, \ uart_altera_jtag_init, \ NULL, \ &uart_altera_jtag_device_data_##n, \ &uart_altera_jtag_dev_cfg_##n, \ PRE_KERNEL_1, \ CONFIG_SERIAL_INIT_PRIORITY, \ &uart_altera_jtag_driver_api); #endif /* CONFIG_UART_ALTERA_JTAG_HAL */ DT_INST_FOREACH_STATUS_OKAY(UART_ALTERA_JTAG_DEVICE_INIT)