You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
988 lines
29 KiB
988 lines
29 KiB
/* |
|
* 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 : 11. UART Core |
|
* Limitations: |
|
* 1. User should consider to always use polling mode, as IP core does not have fifo. |
|
* So IP can only send/receive 1 character at a time. |
|
* 2. CTS and RTS is purely software controlled. Assertion might not be on time. |
|
* 3. Full duplex mode is not supported. |
|
*/ |
|
|
|
#define DT_DRV_COMPAT altr_uart |
|
|
|
#include <zephyr/kernel.h> |
|
#include <zephyr/drivers/uart.h> |
|
|
|
#include <zephyr/drivers/serial/uart_altera.h> |
|
|
|
#ifdef CONFIG_UART_LINE_CTRL |
|
#ifndef CONFIG_UART_INTERRUPT_DRIVEN |
|
/* CTS and RTS is purely software controlled. */ |
|
#error "uart_altera.c: Must enable UART_INTERRUPT_DRIVEN for line control!" |
|
#endif |
|
#endif |
|
|
|
/* register offsets */ |
|
#define ALTERA_AVALON_UART_OFFSET (0x4) |
|
|
|
#define ALTERA_AVALON_UART_RXDATA_REG_OFFSET (0 * ALTERA_AVALON_UART_OFFSET) |
|
#define ALTERA_AVALON_UART_TXDATA_REG_OFFSET (1 * ALTERA_AVALON_UART_OFFSET) |
|
#define ALTERA_AVALON_UART_STATUS_REG_OFFSET (2 * ALTERA_AVALON_UART_OFFSET) |
|
#define ALTERA_AVALON_UART_CONTROL_REG_OFFSET (3 * ALTERA_AVALON_UART_OFFSET) |
|
#define ALTERA_AVALON_UART_DIVISOR_REG_OFFSET (4 * ALTERA_AVALON_UART_OFFSET) |
|
#define ALTERA_AVALON_UART_EOP_REG_OFFSET (5 * ALTERA_AVALON_UART_OFFSET) |
|
|
|
/*status register mask */ |
|
#define ALTERA_AVALON_UART_STATUS_PE_MSK (0x1) |
|
#define ALTERA_AVALON_UART_STATUS_FE_MSK (0x2) |
|
#define ALTERA_AVALON_UART_STATUS_BRK_MSK (0x4) |
|
#define ALTERA_AVALON_UART_STATUS_ROE_MSK (0x8) |
|
#define ALTERA_AVALON_UART_STATUS_TMT_MSK (0x20) |
|
#define ALTERA_AVALON_UART_STATUS_TRDY_MSK (0x40) |
|
#define ALTERA_AVALON_UART_STATUS_RRDY_MSK (0x80) |
|
#define ALTERA_AVALON_UART_STATUS_DCTS_MSK (0x400) |
|
#define ALTERA_AVALON_UART_STATUS_CTS_MSK (0x800) |
|
#define ALTERA_AVALON_UART_STATUS_E_MSK (0x100) |
|
#define ALTERA_AVALON_UART_STATUS_EOP_MSK (0x1000) |
|
|
|
/* control register mask */ |
|
#define ALTERA_AVALON_UART_CONTROL_TMT_MSK (0x20) |
|
#define ALTERA_AVALON_UART_CONTROL_TRDY_MSK (0x40) |
|
#define ALTERA_AVALON_UART_CONTROL_RRDY_MSK (0x80) |
|
#define ALTERA_AVALON_UART_CONTROL_E_MSK (0x100) |
|
#define ALTERA_AVALON_UART_CONTROL_DCTS_MSK (0x400) |
|
#define ALTERA_AVALON_UART_CONTROL_RTS_MSK (0x800) |
|
#define ALTERA_AVALON_UART_CONTROL_EOP_MSK (0x1000) |
|
|
|
/* defined values */ |
|
#define UART_ALTERA_NO_ERROR (0u) |
|
#define ALTERA_AVALON_UART_CLEAR_STATUS_VAL (0u) |
|
#define ALTERA_AVALON_UART_PENDING_MASK (ALTERA_AVALON_UART_STATUS_RRDY_MSK | \ |
|
ALTERA_AVALON_UART_STATUS_TRDY_MSK | ALTERA_AVALON_UART_STATUS_E_MSK | \ |
|
ALTERA_AVALON_UART_STATUS_EOP_MSK) |
|
|
|
/***********************/ |
|
/* configuration flags */ |
|
/* |
|
* The value ALT_AVALON_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 ALT_AVALON_UART_FB 0x1 |
|
|
|
/* |
|
* The value ALT_AVALON_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 ALT_AVALON_UART_FC 0x2 |
|
|
|
/* end of configuration flags */ |
|
/******************************/ |
|
|
|
/* device data */ |
|
struct uart_altera_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_ALTERA_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_ALTERA_EOP */ |
|
#ifdef CONFIG_UART_ALTERA_LINE_CTRL_WORKAROUND |
|
uint8_t dcts_rising; |
|
#endif /*CONFIG_UART_ALTERA_LINE_CTRL_WORKAROUND*/ |
|
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_altera_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_altera_irq_update(const struct device *dev); |
|
static int uart_altera_irq_tx_ready(const struct device *dev); |
|
static int uart_altera_irq_rx_ready(const struct device *dev); |
|
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */ |
|
|
|
/** |
|
* @brief Poll the device for input. |
|
* |
|
* This is a non-blocking function. |
|
* |
|
* @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_altera_poll_in(const struct device *dev, unsigned char *p_char) |
|
{ |
|
const struct uart_altera_device_config *config = dev->config; |
|
struct uart_altera_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 + ALTERA_AVALON_UART_STATUS_REG_OFFSET); |
|
if (status & ALTERA_AVALON_UART_STATUS_RRDY_MSK) { |
|
/* got a character */ |
|
*p_char = sys_read32(config->base + ALTERA_AVALON_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. |
|
* |
|
* @param dev UART device instance |
|
* @param c Character to send |
|
*/ |
|
static void uart_altera_poll_out(const struct device *dev, unsigned char c) |
|
{ |
|
const struct uart_altera_device_config *config = dev->config; |
|
struct uart_altera_device_data *data = dev->data; |
|
uint32_t status; |
|
|
|
k_spinlock_key_t key = k_spin_lock(&data->lock); |
|
|
|
do { |
|
/* wait until uart is free to transmit.*/ |
|
status = sys_read32(config->base + ALTERA_AVALON_UART_STATUS_REG_OFFSET); |
|
} while ((status & ALTERA_AVALON_UART_STATUS_TRDY_MSK) == 0); |
|
|
|
sys_write32(c, config->base + ALTERA_AVALON_UART_TXDATA_REG_OFFSET); |
|
|
|
k_spin_unlock(&data->lock, key); |
|
} |
|
|
|
/** |
|
* @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_init(const struct device *dev) |
|
{ |
|
#ifdef CONFIG_UART_INTERRUPT_DRIVEN |
|
struct uart_altera_device_data *data = dev->data; |
|
const struct uart_altera_device_config *config = dev->config; |
|
/* clear status to ensure, that interrupts are not triggered due to old status. */ |
|
sys_write32(ALTERA_AVALON_UART_CLEAR_STATUS_VAL, config->base |
|
+ ALTERA_AVALON_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 = ALTERA_AVALON_UART_CONTROL_DCTS_MSK; |
|
#endif /* CONFIG_UART_LINE_CTRL */ |
|
|
|
sys_write32(data->control_val, config->base + ALTERA_AVALON_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_altera_err_check(const struct device *dev) |
|
{ |
|
struct uart_altera_device_data *data = dev->data; |
|
int err = UART_ALTERA_NO_ERROR; |
|
|
|
#ifndef CONFIG_UART_INTERRUPT_DRIVEN |
|
const struct uart_altera_device_config *config = dev->config; |
|
k_spinlock_key_t key = k_spin_lock(&data->lock); |
|
|
|
data->status_act = sys_read32(config->base + ALTERA_AVALON_UART_STATUS_REG_OFFSET); |
|
#endif |
|
|
|
if (data->status_act & ALTERA_AVALON_UART_STATUS_E_MSK) { |
|
if (data->status_act & ALTERA_AVALON_UART_STATUS_PE_MSK) { |
|
err |= UART_ERROR_PARITY; |
|
} |
|
|
|
if (data->status_act & ALTERA_AVALON_UART_STATUS_FE_MSK) { |
|
err |= UART_ERROR_FRAMING; |
|
} |
|
|
|
if (data->status_act & ALTERA_AVALON_UART_STATUS_BRK_MSK) { |
|
err |= UART_BREAK; |
|
} |
|
|
|
if (data->status_act & ALTERA_AVALON_UART_STATUS_ROE_MSK) { |
|
err |= UART_ERROR_OVERRUN; |
|
} |
|
} |
|
|
|
#ifndef CONFIG_UART_INTERRUPT_DRIVEN |
|
/* clear status */ |
|
sys_write32(ALTERA_AVALON_UART_CLEAR_STATUS_VAL, config->base |
|
+ ALTERA_AVALON_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. |
|
* @param cfg_stored : The original configuration. |
|
* @param cfg_in : The input configuration. |
|
* @return true if only baudrate is changed. otherwise false. |
|
*/ |
|
static bool uart_altera_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_altera_configure(const struct device *dev, |
|
const struct uart_config *cfg_in) |
|
{ |
|
const struct uart_altera_device_config *config = dev->config; |
|
struct uart_altera_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_altera_check_configuration(cfg_stored, cfg_in) |
|
&& !(config->flags & ALT_AVALON_UART_FB)) { |
|
/* calculate and set baudrate. */ |
|
divisor_val = (CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC/cfg_in->baudrate) - 1; |
|
sys_write32(divisor_val, config->base + ALTERA_AVALON_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; |
|
} |
|
|
|
/** |
|
* @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_altera_config_get(const struct device *dev, |
|
struct uart_config *cfg_out) |
|
{ |
|
const struct uart_altera_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; |
|
} |
|
#endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */ |
|
|
|
#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! |
|
* IP has no fifo. Hence only 1 data can be sent at a time! |
|
* |
|
* @param dev UART device struct |
|
* @param tx_data Data to transmit |
|
* @param size Number of bytes to send (unused) |
|
* |
|
* @return Number of bytes sent |
|
*/ |
|
static int uart_altera_fifo_fill(const struct device *dev, |
|
const uint8_t *tx_data, |
|
int size) |
|
{ |
|
ARG_UNUSED(size); |
|
|
|
const struct uart_altera_device_config *config = dev->config; |
|
struct uart_altera_device_data *data = dev->data; |
|
int ret_val; |
|
|
|
/* 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; |
|
} |
|
|
|
k_spinlock_key_t key = k_spin_lock(&data->lock); |
|
|
|
if (data->status_act & ALTERA_AVALON_UART_STATUS_TRDY_MSK) { |
|
sys_write32(*tx_data, config->base + ALTERA_AVALON_UART_TXDATA_REG_OFFSET); |
|
ret_val = 1; |
|
/* function may be called in a loop. update the actual status! */ |
|
data->status_act = sys_read32(config->base + ALTERA_AVALON_UART_STATUS_REG_OFFSET); |
|
} else { |
|
ret_val = 0; |
|
} |
|
|
|
#ifdef CONFIG_UART_ALTERA_LINE_CTRL_WORKAROUND |
|
/* clear and CTS rising edge! */ |
|
data->dcts_rising = 0; |
|
#endif /* CONFIG_UART_ALTERA_LINE_CTRL_WORKAROUND */ |
|
|
|
k_spin_unlock(&data->lock, key); |
|
|
|
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. |
|
* IP has no fifo. Hence only 1 data can be read at a time! |
|
* |
|
* @param dev UART device struct |
|
* @param rx_data Data container |
|
* @param size Container size |
|
* |
|
* @return Number of bytes read |
|
*/ |
|
static int uart_altera_fifo_read(const struct device *dev, uint8_t *rx_data, |
|
const int size) |
|
{ |
|
ARG_UNUSED(size); |
|
|
|
const struct uart_altera_device_config *config = dev->config; |
|
struct uart_altera_device_data *data = dev->data; |
|
int ret_val; |
|
|
|
/* 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; |
|
} |
|
|
|
k_spinlock_key_t key = k_spin_lock(&data->lock); |
|
|
|
if (data->status_act & ALTERA_AVALON_UART_STATUS_RRDY_MSK) { |
|
*rx_data = sys_read32(config->base + ALTERA_AVALON_UART_RXDATA_REG_OFFSET); |
|
ret_val = 1; |
|
|
|
/* function may be called in a loop. update the actual status! */ |
|
data->status_act = sys_read32(config->base + ALTERA_AVALON_UART_STATUS_REG_OFFSET); |
|
} else { |
|
ret_val = 0; |
|
} |
|
|
|
#ifdef CONFIG_UART_ALTERA_LINE_CTRL_WORKAROUND |
|
/* assert RTS as soon as rx data is read, as IP has no fifo. */ |
|
data->status_act = sys_read32(config->base + ALTERA_AVALON_UART_STATUS_REG_OFFSET); |
|
if (((data->status_act & ALTERA_AVALON_UART_STATUS_RRDY_MSK) == 0) |
|
&& (data->status_act & ALTERA_AVALON_UART_STATUS_CTS_MSK)) { |
|
data->control_val |= ALTERA_AVALON_UART_CONTROL_RTS_MSK; |
|
sys_write32(data->control_val, config->base |
|
+ ALTERA_AVALON_UART_CONTROL_REG_OFFSET); |
|
} |
|
#endif /* CONFIG_UART_ALTERA_LINE_CTRL_WORKAROUND */ |
|
|
|
k_spin_unlock(&data->lock, key); |
|
|
|
return ret_val; |
|
} |
|
|
|
/** |
|
* @brief Enable TX interrupt |
|
* |
|
* @param dev UART device struct |
|
*/ |
|
static void uart_altera_irq_tx_enable(const struct device *dev) |
|
{ |
|
struct uart_altera_device_data *data = dev->data; |
|
const struct uart_altera_device_config *config = dev->config; |
|
|
|
k_spinlock_key_t key = k_spin_lock(&data->lock); |
|
|
|
data->control_val |= ALTERA_AVALON_UART_CONTROL_TRDY_MSK; |
|
|
|
#ifdef CONFIG_UART_LINE_CTRL |
|
/* also enable RTS, if flow control is enabled. */ |
|
data->control_val |= ALTERA_AVALON_UART_CONTROL_RTS_MSK; |
|
#endif |
|
|
|
sys_write32(data->control_val, config->base + ALTERA_AVALON_UART_CONTROL_REG_OFFSET); |
|
|
|
k_spin_unlock(&data->lock, key); |
|
} |
|
|
|
/** |
|
* @brief Disable TX interrupt |
|
* |
|
* @param dev UART device struct |
|
*/ |
|
static void uart_altera_irq_tx_disable(const struct device *dev) |
|
{ |
|
struct uart_altera_device_data *data = dev->data; |
|
const struct uart_altera_device_config *config = dev->config; |
|
|
|
k_spinlock_key_t key = k_spin_lock(&data->lock); |
|
|
|
data->control_val &= ~ALTERA_AVALON_UART_CONTROL_TRDY_MSK; |
|
|
|
#ifdef CONFIG_UART_LINE_CTRL |
|
/* also disable RTS, if flow control is enabled. */ |
|
data->control_val &= ~ALTERA_AVALON_UART_CONTROL_RTS_MSK; |
|
#endif |
|
|
|
sys_write32(data->control_val, config->base + ALTERA_AVALON_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_altera_irq_tx_ready(const struct device *dev) |
|
{ |
|
struct uart_altera_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 & ALTERA_AVALON_UART_CONTROL_TRDY_MSK) { |
|
/* IP core does not have fifo. Wait until tx data is completely shifted. */ |
|
if (data->status_act & ALTERA_AVALON_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 & ALTERA_AVALON_UART_STATUS_CTS_MSK) == 0) { |
|
ret_val = 0; |
|
} |
|
#ifdef CONFIG_UART_ALTERA_LINE_CTRL_WORKAROUND |
|
if (data->dcts_rising == 0) { |
|
ret_val = 0; |
|
} |
|
#endif /* CONFIG_UART_ALTERA_LINE_CTRL_WORKAROUND */ |
|
|
|
#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_altera_irq_tx_complete(const struct device *dev) |
|
{ |
|
struct uart_altera_device_data *data = dev->data; |
|
int ret_val = 0; |
|
|
|
k_spinlock_key_t key = k_spin_lock(&data->lock); |
|
|
|
if (data->status_act & ALTERA_AVALON_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_altera_irq_rx_enable(const struct device *dev) |
|
{ |
|
struct uart_altera_device_data *data = dev->data; |
|
const struct uart_altera_device_config *config = dev->config; |
|
|
|
k_spinlock_key_t key = k_spin_lock(&data->lock); |
|
|
|
data->control_val |= ALTERA_AVALON_UART_CONTROL_RRDY_MSK; |
|
sys_write32(data->control_val, config->base + ALTERA_AVALON_UART_CONTROL_REG_OFFSET); |
|
|
|
k_spin_unlock(&data->lock, key); |
|
} |
|
|
|
/** |
|
* @brief Disable RX interrupt |
|
* |
|
* @param dev UART device struct |
|
*/ |
|
static void uart_altera_irq_rx_disable(const struct device *dev) |
|
{ |
|
struct uart_altera_device_data *data = dev->data; |
|
const struct uart_altera_device_config *config = dev->config; |
|
|
|
k_spinlock_key_t key = k_spin_lock(&data->lock); |
|
|
|
data->control_val &= ~ALTERA_AVALON_UART_CONTROL_RRDY_MSK; |
|
sys_write32(data->control_val, config->base + ALTERA_AVALON_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_altera_irq_rx_ready(const struct device *dev) |
|
{ |
|
struct uart_altera_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 & ALTERA_AVALON_UART_CONTROL_RRDY_MSK) { |
|
/* check for space in rx data register */ |
|
if (data->status_act & ALTERA_AVALON_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_altera_irq_update(const struct device *dev) |
|
{ |
|
struct uart_altera_device_data *data = dev->data; |
|
const struct uart_altera_device_config *config = dev->config; |
|
|
|
k_spinlock_key_t key = k_spin_lock(&data->lock); |
|
|
|
data->status_act = sys_read32(config->base + ALTERA_AVALON_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_altera_irq_is_pending(const struct device *dev) |
|
{ |
|
struct uart_altera_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 & ALTERA_AVALON_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_altera_irq_callback_set(const struct device *dev, |
|
uart_irq_callback_user_data_t cb, |
|
void *cb_data) |
|
{ |
|
struct uart_altera_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); |
|
|
|
#ifdef CONFIG_UART_ALTERA_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_ALTERA_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_altera_dcts_isr(const struct device *dev) |
|
{ |
|
struct uart_altera_device_data *data = dev->data; |
|
const struct uart_altera_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 & ALTERA_AVALON_UART_STATUS_CTS_MSK) { |
|
#ifdef CONFIG_UART_ALTERA_LINE_CTRL_WORKAROUND |
|
data->dcts_rising = 1; |
|
#endif /* CONFIG_UART_ALTERA_LINE_CTRL_WORKAROUND */ |
|
|
|
/* check if device is ready to receive character */ |
|
if ((data->status_act & ALTERA_AVALON_UART_STATUS_RRDY_MSK) == 0) { |
|
/* Assert RTS to inform other UART. */ |
|
data->control_val |= ALTERA_AVALON_UART_CONTROL_RTS_MSK; |
|
sys_write32(data->control_val, config->base |
|
+ ALTERA_AVALON_UART_CONTROL_REG_OFFSET); |
|
} |
|
} else { |
|
/* other UART deasserts RTS */ |
|
if (data->status_act & ALTERA_AVALON_UART_STATUS_TMT_MSK) { |
|
/* only deasserts if not transmitting. */ |
|
data->control_val &= ~ALTERA_AVALON_UART_CONTROL_RTS_MSK; |
|
sys_write32(data->control_val, config->base |
|
+ ALTERA_AVALON_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_altera_isr(const struct device *dev) |
|
{ |
|
struct uart_altera_device_data *data = dev->data; |
|
const struct uart_altera_device_config *config = dev->config; |
|
|
|
uart_irq_callback_user_data_t callback = data->cb; |
|
|
|
/* Pre ISR */ |
|
#ifdef CONFIG_UART_ALTERA_LINE_CTRL_WORKAROUND |
|
/* deassert RTS as soon as rx data is received, as IP has no fifo. */ |
|
data->status_act = sys_read32(config->base + ALTERA_AVALON_UART_STATUS_REG_OFFSET); |
|
if (data->status_act & ALTERA_AVALON_UART_STATUS_RRDY_MSK) { |
|
data->control_val &= ~ALTERA_AVALON_UART_CONTROL_RTS_MSK; |
|
sys_write32(data->control_val, config->base |
|
+ ALTERA_AVALON_UART_CONTROL_REG_OFFSET); |
|
} |
|
#endif /* CONFIG_UART_ALTERA_LINE_CTRL_WORKAROUND */ |
|
|
|
if (callback) { |
|
callback(dev, data->cb_data); |
|
} |
|
|
|
/* Post ISR */ |
|
#if CONFIG_UART_ALTERA_EOP |
|
data->status_act = sys_read32(config->base + ALTERA_AVALON_UART_STATUS_REG_OFFSET); |
|
|
|
if (data->status_act & ALTERA_AVALON_UART_STATUS_EOP_MSK) { |
|
callback = data->cb_eop; |
|
if (callback) { |
|
callback(dev, data->cb_data_eop); |
|
} |
|
} |
|
#endif /* CONFIG_UART_ALTERA_EOP */ |
|
|
|
#ifdef CONFIG_UART_LINE_CTRL |
|
/* handles RTS/CTS signal */ |
|
if (data->status_act & ALTERA_AVALON_UART_STATUS_DCTS_MSK) { |
|
uart_altera_dcts_isr(dev); |
|
} |
|
#endif |
|
|
|
/* clear status after all interrupts are handled. */ |
|
sys_write32(ALTERA_AVALON_UART_CLEAR_STATUS_VAL, config->base |
|
+ ALTERA_AVALON_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_altera_drv_cmd(const struct device *dev, uint32_t cmd, |
|
uint32_t p) |
|
{ |
|
struct uart_altera_device_data *data = dev->data; |
|
#if CONFIG_UART_ALTERA_EOP |
|
const struct uart_altera_device_config *config = dev->config; |
|
#endif |
|
int ret_val = -ENOTSUP; |
|
k_spinlock_key_t key = k_spin_lock(&data->lock); |
|
|
|
switch (cmd) { |
|
#if CONFIG_UART_ALTERA_EOP |
|
case CMD_ENABLE_EOP: |
|
/* enable EOP interrupt */ |
|
data->control_val |= ALTERA_AVALON_UART_CONTROL_EOP_MSK; |
|
sys_write32(data->control_val, config->base |
|
+ ALTERA_AVALON_UART_CONTROL_REG_OFFSET); |
|
|
|
/* set EOP character */ |
|
sys_write32((uint8_t) p, config->base + ALTERA_AVALON_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 &= ~ALTERA_AVALON_UART_CONTROL_EOP_MSK; |
|
sys_write32(data->control_val, config->base |
|
+ ALTERA_AVALON_UART_CONTROL_REG_OFFSET); |
|
|
|
/* clear call back */ |
|
data->cb_eop = NULL; |
|
data->cb_data_eop = NULL; |
|
ret_val = 0; |
|
break; |
|
#endif /* CONFIG_UART_ALTERA_EOP */ |
|
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 DEVICE_API(uart, uart_altera_driver_api) = { |
|
.poll_in = uart_altera_poll_in, |
|
.poll_out = uart_altera_poll_out, |
|
.err_check = uart_altera_err_check, |
|
#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE |
|
.configure = uart_altera_configure, |
|
.config_get = uart_altera_config_get, |
|
#endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */ |
|
|
|
#ifdef CONFIG_UART_INTERRUPT_DRIVEN |
|
.fifo_fill = uart_altera_fifo_fill, |
|
.fifo_read = uart_altera_fifo_read, |
|
.irq_tx_enable = uart_altera_irq_tx_enable, |
|
.irq_tx_disable = uart_altera_irq_tx_disable, |
|
.irq_tx_ready = uart_altera_irq_tx_ready, |
|
.irq_tx_complete = uart_altera_irq_tx_complete, |
|
.irq_rx_enable = uart_altera_irq_rx_enable, |
|
.irq_rx_disable = uart_altera_irq_rx_disable, |
|
.irq_rx_ready = uart_altera_irq_rx_ready, |
|
.irq_is_pending = uart_altera_irq_is_pending, |
|
.irq_update = uart_altera_irq_update, |
|
.irq_callback_set = uart_altera_irq_callback_set, |
|
#endif |
|
|
|
#ifdef CONFIG_UART_DRV_CMD |
|
.drv_cmd = uart_altera_drv_cmd, |
|
#endif |
|
}; |
|
|
|
#ifdef CONFIG_UART_INTERRUPT_DRIVEN |
|
|
|
#define UART_ALTERA_IRQ_CONFIG_FUNC(n) \ |
|
static void uart_altera_irq_config_func_##n(const struct device *dev) \ |
|
{ \ |
|
IRQ_CONNECT(DT_INST_IRQN(n), \ |
|
DT_INST_IRQ(n, priority), \ |
|
uart_altera_isr, \ |
|
DEVICE_DT_INST_GET(n), 0); \ |
|
\ |
|
irq_enable(DT_INST_IRQN(n)); \ |
|
} |
|
|
|
#define UART_ALTERA_IRQ_CONFIG_INIT(n) \ |
|
.irq_config_func = uart_altera_irq_config_func_##n, \ |
|
.irq_num = DT_INST_IRQN(n), |
|
|
|
#else |
|
|
|
#define UART_ALTERA_IRQ_CONFIG_FUNC(n) |
|
#define UART_ALTERA_IRQ_CONFIG_INIT(n) |
|
|
|
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */ |
|
|
|
#define UART_ALTERA_DEVICE_INIT(n) \ |
|
UART_ALTERA_IRQ_CONFIG_FUNC(n) \ |
|
static struct uart_altera_device_data uart_altera_dev_data_##n = { \ |
|
.uart_cfg = \ |
|
{ \ |
|
.baudrate = DT_INST_PROP(n, current_speed), \ |
|
.parity = DT_INST_ENUM_IDX(n, parity), \ |
|
.stop_bits = DT_INST_ENUM_IDX(n, stop_bits), \ |
|
.data_bits = DT_INST_ENUM_IDX(n, data_bits), \ |
|
.flow_ctrl = DT_INST_PROP(n, hw_flow_control) ? \ |
|
UART_CFG_FLOW_CTRL_RTS_CTS : \ |
|
UART_CFG_FLOW_CTRL_NONE, \ |
|
}, \ |
|
}; \ |
|
\ |
|
static const struct uart_altera_device_config uart_altera_dev_cfg_##n = { \ |
|
.base = DT_INST_REG_ADDR(n), \ |
|
.flags = ((DT_INST_PROP(n, fixed_baudrate)?ALT_AVALON_UART_FB:0) \ |
|
|(DT_INST_PROP(n, hw_flow_control)?ALT_AVALON_UART_FC:0)), \ |
|
UART_ALTERA_IRQ_CONFIG_INIT(n) \ |
|
}; \ |
|
\ |
|
DEVICE_DT_INST_DEFINE(n, \ |
|
uart_altera_init, \ |
|
NULL, \ |
|
&uart_altera_dev_data_##n, \ |
|
&uart_altera_dev_cfg_##n, \ |
|
PRE_KERNEL_1, \ |
|
CONFIG_SERIAL_INIT_PRIORITY, \ |
|
&uart_altera_driver_api); |
|
|
|
DT_INST_FOREACH_STATUS_OKAY(UART_ALTERA_DEVICE_INIT)
|
|
|