/* stellarisUartDrv.c - Stellaris UART driver */ #define DT_DRV_COMPAT ti_stellaris_uart /* * Copyright (c) 2013-2015 Wind River Systems, Inc. * * SPDX-License-Identifier: Apache-2.0 */ /** * @brief Driver for Stellaris UART * * Driver for Stellaris UART found namely on TI LM3S6965 board. It is similar to * an 16550 in functionality, but is not register-compatible. * It is also register-compatible with the UART found on TI CC2650 SoC, * so it can be used for boards using it, like the TI SensorTag. * * There is only support for poll-mode, so it can only be used with the printk * and STDOUT_CONSOLE APIs. */ #include #include #include #include #include #include #include #include /* definitions */ /* Stellaris UART module */ struct _uart { uint32_t dr; union { uint32_t _sr; uint32_t _cr; } u1; uint8_t _res1[0x010]; uint32_t fr; uint8_t _res2[0x04]; uint32_t ilpr; uint32_t ibrd; uint32_t fbrd; uint32_t lcrh; uint32_t ctl; uint32_t ifls; uint32_t im; uint32_t ris; uint32_t mis; uint32_t icr; uint8_t _res3[0xf8c]; uint32_t peripd_id4; uint32_t peripd_id5; uint32_t peripd_id6; uint32_t peripd_id7; uint32_t peripd_id0; uint32_t peripd_id1; uint32_t peripd_id2; uint32_t peripd_id3; uint32_t p_cell_id0; uint32_t p_cell_id1; uint32_t p_cell_id2; uint32_t p_cell_id3; }; struct uart_stellaris_config { volatile struct _uart *uart; uint32_t sys_clk_freq; #ifdef CONFIG_UART_INTERRUPT_DRIVEN uart_irq_config_func_t irq_config_func; #endif }; /* Device data structure */ struct uart_stellaris_dev_data_t { uint32_t baud_rate; /* Baud rate */ #ifdef CONFIG_UART_INTERRUPT_DRIVEN uart_irq_callback_user_data_t cb; /**< Callback function pointer */ void *cb_data; /**< Callback function arg */ #endif }; /* bits */ #define UARTFR_BUSY 0x00000008 #define UARTFR_RXFE 0x00000010 #define UARTFR_TXFF 0x00000020 #define UARTFR_RXFF 0x00000040 #define UARTFR_TXFE 0x00000080 #define UARTLCRH_FEN 0x00000010 #define UARTLCRH_WLEN 0x00000060 #define UARTCTL_UARTEN 0x00000001 #define UARTCTL_LBE 0x00000800 #define UARTCTL_TXEN 0x00000100 #define UARTCTL_RXEN 0x00000200 #define UARTTIM_RXIM 0x00000010 #define UARTTIM_TXIM 0x00000020 #define UARTTIM_RTIM 0x00000040 #define UARTTIM_FEIM 0x00000080 #define UARTTIM_PEIM 0x00000100 #define UARTTIM_BEIM 0x00000200 #define UARTTIM_OEIM 0x00000400 #define UARTMIS_RXMIS 0x00000010 #define UARTMIS_TXMIS 0x00000020 static const struct uart_driver_api uart_stellaris_driver_api; /** * @brief Set the baud rate * * This routine set the given baud rate for the UART. * * @param dev UART device struct * @param baudrate Baud rate * @param sys_clk_freq_hz System clock frequency in Hz */ static void baudrate_set(const struct device *dev, uint32_t baudrate, uint32_t sys_clk_freq_hz) { const struct uart_stellaris_config *config = dev->config; uint32_t brdi, brdf, div, rem; /* upon reset, the system clock uses the internal OSC @ 12MHz */ div = (baudrate * 16U); rem = sys_clk_freq_hz % div; /* * floating part of baud rate (LM3S6965 p.433), equivalent to * [float part of (SYSCLK / div)] * 64 + 0.5 */ brdf = ((((rem * 64U) << 1) / div) + 1) >> 1; /* integer part of baud rate (LM3S6965 p.433) */ brdi = sys_clk_freq_hz / div; /* * those registers are 32-bit, but the reserved bits should be * preserved */ config->uart->ibrd = (uint16_t)(brdi & 0xffff); /* 16 bits */ config->uart->fbrd = (uint8_t)(brdf & 0x3f); /* 6 bits */ } /** * @brief Enable the UART * * This routine enables the given UART. * * @param dev UART device struct */ static inline void enable(const struct device *dev) { const struct uart_stellaris_config *config = dev->config; config->uart->ctl |= UARTCTL_UARTEN; } /** * @brief Disable the UART * * This routine disables the given UART. * * @param dev UART device struct */ static inline void disable(const struct device *dev) { const struct uart_stellaris_config *config = dev->config; config->uart->ctl &= ~UARTCTL_UARTEN; /* ensure transmissions are complete */ while (config->uart->fr & UARTFR_BUSY) { } /* flush the FIFOs by disabling them */ config->uart->lcrh &= ~UARTLCRH_FEN; } /* * no stick parity * 8-bit frame * FIFOs disabled * one stop bit * parity disabled * send break off */ #define LINE_CONTROL_DEFAULTS UARTLCRH_WLEN /** * @brief Set the default UART line controls * * This routine sets the given UART's line controls to their default settings. * * @param dev UART device struct */ static inline void line_control_defaults_set(const struct device *dev) { const struct uart_stellaris_config *config = dev->config; config->uart->lcrh = LINE_CONTROL_DEFAULTS; } /** * @brief Initialize UART channel * * This routine is called to reset the chip in a quiescent state. * It is assumed that this function is called only once per UART. * * @param dev UART device struct * * @return 0 */ static int uart_stellaris_init(const struct device *dev) { struct uart_stellaris_dev_data_t *data = dev->data; const struct uart_stellaris_config *config = dev->config; disable(dev); baudrate_set(dev, data->baud_rate, config->sys_clk_freq); line_control_defaults_set(dev); enable(dev); #ifdef CONFIG_UART_INTERRUPT_DRIVEN config->irq_config_func(dev); #endif return 0; } /** * @brief Get the UART transmit ready status * * This routine returns the given UART's transmit ready status. * * @param dev UART device struct * * @return 0 if ready to transmit, 1 otherwise */ static int poll_tx_ready(const struct device *dev) { const struct uart_stellaris_config *config = dev->config; return (config->uart->fr & UARTFR_TXFE); } /** * @brief Poll the device for input. * * @param dev UART device struct * @param c Pointer to character * * @return 0 if a character arrived, -1 if the input buffer if empty. */ static int uart_stellaris_poll_in(const struct device *dev, unsigned char *c) { const struct uart_stellaris_config *config = dev->config; if (config->uart->fr & UARTFR_RXFE) { return (-1); } /* got a character */ *c = (unsigned char)config->uart->dr; return 0; } /** * @brief Output a character in polled mode. * * Checks if the transmitter is empty. If empty, a character is written to * the data register. * * @param dev UART device struct * @param c Character to send */ static void uart_stellaris_poll_out(const struct device *dev, unsigned char c) { const struct uart_stellaris_config *config = dev->config; while (!poll_tx_ready(dev)) { } /* send a character */ config->uart->dr = (uint32_t)c; } #if CONFIG_UART_INTERRUPT_DRIVEN /** * @brief Fill FIFO with data * * @param dev UART device struct * @param tx_data Data to transmit * @param len Number of bytes to send * * @return Number of bytes sent */ static int uart_stellaris_fifo_fill(const struct device *dev, const uint8_t *tx_data, int len) { const struct uart_stellaris_config *config = dev->config; uint8_t num_tx = 0U; while ((len - num_tx > 0) && ((config->uart->fr & UARTFR_TXFF) == 0U)) { config->uart->dr = (uint32_t)tx_data[num_tx++]; } return (int)num_tx; } /** * @brief Read data from FIFO * * @param dev UART device struct * @param rx_data Pointer to data container * @param size Container size * * @return Number of bytes read */ static int uart_stellaris_fifo_read(const struct device *dev, uint8_t *rx_data, const int size) { const struct uart_stellaris_config *config = dev->config; uint8_t num_rx = 0U; while ((size - num_rx > 0) && ((config->uart->fr & UARTFR_RXFE) == 0U)) { rx_data[num_rx++] = (uint8_t)config->uart->dr; } return num_rx; } /** * @brief Enable TX interrupt * * @param dev UART device struct */ static void uart_stellaris_irq_tx_enable(const struct device *dev) { static uint8_t first_time = 1U; /* used to allow the first transmission */ uint32_t saved_ctl; /* saved UARTCTL (control) register */ uint32_t saved_ibrd; /* saved UARTIBRD (integer baud rate) register */ uint32_t saved_fbrd; /* saved UARTFBRD (fractional baud rate) register */ const struct uart_stellaris_config *config = dev->config; if (first_time) { /* * The Tx interrupt will not be set when transmission is first * enabled. * A character has to be transmitted before Tx interrupts will * work, * so send one via loopback mode. */ first_time = 0U; /* save current control and baud rate settings */ saved_ctl = config->uart->ctl; saved_ibrd = config->uart->ibrd; saved_fbrd = config->uart->fbrd; /* send a character with default settings via loopback */ disable(dev); config->uart->fbrd = 0U; config->uart->ibrd = 1U; config->uart->lcrh = 0U; config->uart->ctl = (UARTCTL_UARTEN | UARTCTL_TXEN | UARTCTL_LBE); config->uart->dr = 0U; while (config->uart->fr & UARTFR_BUSY) { } /* restore control and baud rate settings */ disable(dev); config->uart->ibrd = saved_ibrd; config->uart->fbrd = saved_fbrd; line_control_defaults_set(dev); config->uart->ctl = saved_ctl; } config->uart->im |= UARTTIM_TXIM; } /** * @brief Disable TX interrupt in IER * * @param dev UART device struct */ static void uart_stellaris_irq_tx_disable(const struct device *dev) { const struct uart_stellaris_config *config = dev->config; config->uart->im &= ~UARTTIM_TXIM; } /** * @brief Check if Tx IRQ has been raised * * @param dev UART device struct * * @return 1 if a Tx IRQ is pending, 0 otherwise */ static int uart_stellaris_irq_tx_ready(const struct device *dev) { const struct uart_stellaris_config *config = dev->config; return ((config->uart->mis & UARTMIS_TXMIS) == UARTMIS_TXMIS); } /** * @brief Enable RX interrupt in IER * * @param dev UART device struct */ static void uart_stellaris_irq_rx_enable(const struct device *dev) { const struct uart_stellaris_config *config = dev->config; config->uart->im |= UARTTIM_RXIM; } /** * @brief Disable RX interrupt in IER * * @param dev UART device struct */ static void uart_stellaris_irq_rx_disable(const struct device *dev) { const struct uart_stellaris_config *config = dev->config; config->uart->im &= ~UARTTIM_RXIM; } /** * @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_stellaris_irq_rx_ready(const struct device *dev) { const struct uart_stellaris_config *config = dev->config; return ((config->uart->mis & UARTMIS_RXMIS) == UARTMIS_RXMIS); } /** * @brief Enable error interrupts * * @param dev UART device struct */ static void uart_stellaris_irq_err_enable(const struct device *dev) { const struct uart_stellaris_config *config = dev->config; config->uart->im |= (UARTTIM_RTIM | UARTTIM_FEIM | UARTTIM_PEIM | UARTTIM_BEIM | UARTTIM_OEIM); } /** * @brief Disable error interrupts * * @param dev UART device struct */ static void uart_stellaris_irq_err_disable(const struct device *dev) { const struct uart_stellaris_config *config = dev->config; config->uart->im &= ~(UARTTIM_RTIM | UARTTIM_FEIM | UARTTIM_PEIM | UARTTIM_BEIM | UARTTIM_OEIM); } /** * @brief Check if Tx or Rx IRQ is pending * * @param dev UART device struct * * @return 1 if a Tx or Rx IRQ is pending, 0 otherwise */ static int uart_stellaris_irq_is_pending(const struct device *dev) { const struct uart_stellaris_config *config = dev->config; /* Look only at Tx and Rx data interrupt flags */ return ((config->uart->mis & (UARTMIS_RXMIS | UARTMIS_TXMIS)) ? 1 : 0); } /** * @brief Update IRQ status * * @param dev UART device struct * * @return Always 1 */ static int uart_stellaris_irq_update(const struct device *dev) { return 1; } /** * @brief Set the callback function pointer for IRQ. * * @param dev UART device struct * @param cb Callback function pointer. */ static void uart_stellaris_irq_callback_set(const struct device *dev, uart_irq_callback_user_data_t cb, void *cb_data) { struct uart_stellaris_dev_data_t * const dev_data = dev->data; dev_data->cb = cb; dev_data->cb_data = cb_data; } /** * @brief Interrupt service routine. * * This simply calls the callback function, if one exists. * * @param arg Argument to ISR. */ static void uart_stellaris_isr(const struct device *dev) { struct uart_stellaris_dev_data_t * const dev_data = dev->data; if (dev_data->cb) { dev_data->cb(dev, dev_data->cb_data); } } #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ static const struct uart_driver_api uart_stellaris_driver_api = { .poll_in = uart_stellaris_poll_in, .poll_out = uart_stellaris_poll_out, #ifdef CONFIG_UART_INTERRUPT_DRIVEN .fifo_fill = uart_stellaris_fifo_fill, .fifo_read = uart_stellaris_fifo_read, .irq_tx_enable = uart_stellaris_irq_tx_enable, .irq_tx_disable = uart_stellaris_irq_tx_disable, .irq_tx_ready = uart_stellaris_irq_tx_ready, .irq_rx_enable = uart_stellaris_irq_rx_enable, .irq_rx_disable = uart_stellaris_irq_rx_disable, .irq_rx_ready = uart_stellaris_irq_rx_ready, .irq_err_enable = uart_stellaris_irq_err_enable, .irq_err_disable = uart_stellaris_irq_err_disable, .irq_is_pending = uart_stellaris_irq_is_pending, .irq_update = uart_stellaris_irq_update, .irq_callback_set = uart_stellaris_irq_callback_set, #endif }; #ifdef CONFIG_UART_STELLARIS_PORT_0 #ifdef CONFIG_UART_INTERRUPT_DRIVEN static void irq_config_func_0(const struct device *port); #endif static const struct uart_stellaris_config uart_stellaris_dev_cfg_0 = { .uart = (volatile struct _uart *)DT_INST_REG_ADDR(0), .sys_clk_freq = DT_INST_PROP_BY_PHANDLE(0, clocks, clock_frequency), #ifdef CONFIG_UART_INTERRUPT_DRIVEN .irq_config_func = irq_config_func_0, #endif }; static struct uart_stellaris_dev_data_t uart_stellaris_dev_data_0 = { .baud_rate = DT_INST_PROP(0, current_speed), }; DEVICE_DT_INST_DEFINE(0, &uart_stellaris_init, NULL, &uart_stellaris_dev_data_0, &uart_stellaris_dev_cfg_0, PRE_KERNEL_1, CONFIG_SERIAL_INIT_PRIORITY, &uart_stellaris_driver_api); #ifdef CONFIG_UART_INTERRUPT_DRIVEN static void irq_config_func_0(const struct device *dev) { IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), uart_stellaris_isr, DEVICE_DT_INST_GET(0), 0); irq_enable(DT_INST_IRQN(0)); } #endif #endif /* CONFIG_UART_STELLARIS_PORT_0 */ #ifdef CONFIG_UART_STELLARIS_PORT_1 #ifdef CONFIG_UART_INTERRUPT_DRIVEN static void irq_config_func_1(const struct device *port); #endif static struct uart_stellaris_config uart_stellaris_dev_cfg_1 = { .uart = (volatile struct _uart *)DT_INST_REG_ADDR(1), .sys_clk_freq = DT_INST_PROP_BY_PHANDLE(1, clocks, clock_frequency), #ifdef CONFIG_UART_INTERRUPT_DRIVEN .irq_config_func = irq_config_func_1, #endif }; static struct uart_stellaris_dev_data_t uart_stellaris_dev_data_1 = { .baud_rate = DT_INST_PROP(1, current_speed), }; DEVICE_DT_INST_DEFINE(1, &uart_stellaris_init, NULL, &uart_stellaris_dev_data_1, &uart_stellaris_dev_cfg_1, PRE_KERNEL_1, CONFIG_SERIAL_INIT_PRIORITY, &uart_stellaris_driver_api); #ifdef CONFIG_UART_INTERRUPT_DRIVEN static void irq_config_func_1(const struct device *dev) { IRQ_CONNECT(DT_INST_IRQN(1), DT_INST_IRQ(1, priority), uart_stellaris_isr, DEVICE_DT_INST_GET(1), 0); irq_enable(DT_INST_IRQN(1)); } #endif #endif /* CONFIG_UART_STELLARIS_PORT_1 */ #ifdef CONFIG_UART_STELLARIS_PORT_2 #ifdef CONFIG_UART_INTERRUPT_DRIVEN static void irq_config_func_2(const struct device *port); #endif static const struct uart_stellaris_config uart_stellaris_dev_cfg_2 = { .uart = (volatile struct _uart *)DT_INST_REG_ADDR(2), .sys_clk_freq = DT_INST_PROP_BY_PHANDLE(2, clocks, clock_frequency), #ifdef CONFIG_UART_INTERRUPT_DRIVEN .irq_config_func = irq_config_func_2, #endif }; static struct uart_stellaris_dev_data_t uart_stellaris_dev_data_2 = { .baud_rate = DT_INST_PROP(2, current_speed), }; DEVICE_DT_INST_DEFINE(2, &uart_stellaris_init, NULL, &uart_stellaris_dev_data_2, &uart_stellaris_dev_cfg_2, PRE_KERNEL_1, CONFIG_SERIAL_INIT_PRIORITY, &uart_stellaris_driver_api); #ifdef CONFIG_UART_INTERRUPT_DRIVEN static void irq_config_func_2(const struct device *dev) { IRQ_CONNECT(DT_INST_IRQN(2), DT_INST_IRQ(2, priority), uart_stellaris_isr, DEVICE_DT_INST_GET(2), 0); irq_enable(DT_INST_IRQN(2)); } #endif #endif /* CONFIG_UART_STELLARIS_PORT_2 */