| /* |
| * Copyright (c) 2020, Seagate Technology LLC |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| #define DT_DRV_COMPAT nxp_lpc11u6x_uart |
| |
| #include <cmsis_core.h> |
| |
| #include <zephyr/drivers/uart.h> |
| #include <zephyr/drivers/clock_control.h> |
| #include <zephyr/irq.h> |
| |
| #include "uart_lpc11u6x.h" |
| |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart0)) |
| static int lpc11u6x_uart0_poll_in(const struct device *dev, unsigned char *c) |
| { |
| const struct lpc11u6x_uart0_config *cfg = dev->config; |
| |
| if (!(cfg->uart0->lsr & LPC11U6X_UART0_LSR_RDR)) { |
| return -1; |
| } |
| *c = cfg->uart0->rbr; |
| |
| return 0; |
| } |
| |
| static void lpc11u6x_uart0_poll_out(const struct device *dev, unsigned char c) |
| { |
| const struct lpc11u6x_uart0_config *cfg = dev->config; |
| |
| while (!(cfg->uart0->lsr & LPC11U6X_UART0_LSR_THRE)) { |
| } |
| cfg->uart0->thr = c; |
| } |
| |
| static int lpc11u6x_uart0_err_check(const struct device *dev) |
| { |
| const struct lpc11u6x_uart0_config *cfg = dev->config; |
| uint32_t lsr; |
| int ret = 0; |
| |
| lsr = cfg->uart0->lsr; |
| if (lsr & LPC11U6X_UART0_LSR_OE) { |
| ret |= UART_ERROR_OVERRUN; |
| } |
| if (lsr & LPC11U6X_UART0_LSR_PE) { |
| ret |= UART_ERROR_PARITY; |
| } |
| if (lsr & LPC11U6X_UART0_LSR_FE) { |
| ret |= UART_ERROR_FRAMING; |
| } |
| if (lsr & LPC11U6X_UART0_LSR_BI) { |
| ret |= UART_BREAK; |
| } |
| |
| return ret; |
| } |
| |
| static void lpc11u6x_uart0_write_divisor(struct lpc11u6x_uart0_regs *uart0, |
| uint32_t div) |
| { |
| /* Enable access to dll & dlm registers */ |
| uart0->lcr |= LPC11U6X_UART0_LCR_DLAB; |
| uart0->dll = div & 0xFF; |
| uart0->dlm = (div >> 8) & 0xFF; |
| uart0->lcr &= ~LPC11U6X_UART0_LCR_DLAB; |
| } |
| |
| static void lpc11u6x_uart0_write_fdr(struct lpc11u6x_uart0_regs *uart0, |
| uint32_t div, uint32_t mul) |
| { |
| uart0->fdr = (div & 0xF) | ((mul & 0xF) << 4); |
| } |
| |
| static void lpc11u6x_uart0_config_baudrate(const struct device *clk_drv, |
| const struct lpc11u6x_uart0_config *cfg, |
| uint32_t baudrate) |
| { |
| uint32_t div = 1, mul, dl; |
| uint32_t pclk; |
| |
| /* Compute values for fractional baud rate generator. We need to have |
| * a clock that is as close as possible to a multiple of |
| * LPC11U6X_UART0_CLK so that we can have every baudrate that is |
| * a multiple of 9600 |
| */ |
| clock_control_get_rate(clk_drv, (clock_control_subsys_t) cfg->clkid, |
| &pclk); |
| mul = pclk / (pclk % LPC11U6X_UART0_CLK); |
| |
| dl = pclk / (16 * baudrate + 16 * baudrate / mul); |
| |
| /* Configure clock divisor and fractional baudrate generator */ |
| lpc11u6x_uart0_write_divisor(cfg->uart0, dl); |
| lpc11u6x_uart0_write_fdr(cfg->uart0, div, mul); |
| } |
| |
| #ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE |
| static int lpc11u6x_uart0_configure(const struct device *dev, |
| const struct uart_config *cfg) |
| { |
| const struct lpc11u6x_uart0_config *dev_cfg = dev->config; |
| struct lpc11u6x_uart0_data *data = dev->data; |
| uint32_t flags = 0; |
| |
| /* Check that the baudrate is a multiple of 9600 */ |
| if (cfg->baudrate % 9600) { |
| return -ENOTSUP; |
| } |
| |
| switch (cfg->parity) { |
| case UART_CFG_PARITY_NONE: |
| break; |
| case UART_CFG_PARITY_ODD: |
| flags |= LPC11U6X_UART0_LCR_PARTIY_ENABLE | |
| LPC11U6X_UART0_LCR_PARTIY_ODD; |
| break; |
| case UART_CFG_PARITY_EVEN: |
| flags |= LPC11U6X_UART0_LCR_PARTIY_ENABLE | |
| LPC11U6X_UART0_LCR_PARTIY_EVEN; |
| break; |
| case UART_CFG_PARITY_MARK: |
| __fallthrough; |
| case UART_CFG_PARITY_SPACE: |
| return -ENOTSUP; |
| default: |
| return -EINVAL; |
| } |
| |
| switch (cfg->stop_bits) { |
| case UART_CFG_STOP_BITS_0_5: |
| return -ENOTSUP; |
| case UART_CFG_STOP_BITS_1: |
| flags |= LPC11U6X_UART0_LCR_STOP_1BIT; |
| break; |
| case UART_CFG_STOP_BITS_1_5: |
| return -ENOTSUP; |
| case UART_CFG_STOP_BITS_2: |
| flags |= LPC11U6X_UART0_LCR_STOP_2BIT; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| switch (cfg->data_bits) { |
| case UART_CFG_DATA_BITS_5: |
| flags |= LPC11U6X_UART0_LCR_WLS_5BITS; |
| break; |
| case UART_CFG_DATA_BITS_6: |
| flags |= LPC11U6X_UART0_LCR_WLS_6BITS; |
| break; |
| case UART_CFG_DATA_BITS_7: |
| flags |= LPC11U6X_UART0_LCR_WLS_7BITS; |
| break; |
| case UART_CFG_DATA_BITS_8: |
| flags |= LPC11U6X_UART0_LCR_WLS_8BITS; |
| break; |
| case UART_CFG_DATA_BITS_9: |
| return -ENOTSUP; |
| default: |
| return -EINVAL; |
| } |
| |
| if (cfg->flow_ctrl != UART_CFG_FLOW_CTRL_NONE) { |
| return -ENOTSUP; |
| } |
| |
| lpc11u6x_uart0_config_baudrate(dev_cfg->clock_dev, dev_cfg, cfg->baudrate); |
| dev_cfg->uart0->lcr = flags; |
| |
| data->baudrate = cfg->baudrate; |
| data->stop_bits = cfg->stop_bits; |
| data->data_bits = cfg->data_bits; |
| data->flow_ctrl = cfg->flow_ctrl; |
| data->parity = cfg->parity; |
| |
| return 0; |
| } |
| |
| static int lpc11u6x_uart0_config_get(const struct device *dev, |
| struct uart_config *cfg) |
| { |
| struct lpc11u6x_uart0_data *data = dev->data; |
| |
| cfg->baudrate = data->baudrate; |
| cfg->parity = data->parity; |
| cfg->stop_bits = data->stop_bits; |
| cfg->data_bits = data->data_bits; |
| cfg->flow_ctrl = data->flow_ctrl; |
| |
| return 0; |
| } |
| #endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */ |
| |
| #ifdef CONFIG_UART_INTERRUPT_DRIVEN |
| static int lpc11u6x_uart0_fifo_fill(const struct device *dev, |
| const uint8_t *data, |
| const int size) |
| { |
| const struct lpc11u6x_uart0_config *cfg = dev->config; |
| int nr_sent = 0; |
| |
| while (nr_sent < size && (cfg->uart0->lsr & LPC11U6X_UART0_LSR_THRE)) { |
| cfg->uart0->thr = data[nr_sent++]; |
| } |
| |
| return nr_sent; |
| } |
| |
| static int lpc11u6x_uart0_fifo_read(const struct device *dev, uint8_t *data, |
| const int size) |
| { |
| const struct lpc11u6x_uart0_config *cfg = dev->config; |
| int nr_rx = 0; |
| |
| while (nr_rx < size && (cfg->uart0->lsr & LPC11U6X_UART0_LSR_RDR)) { |
| data[nr_rx++] = cfg->uart0->rbr; |
| } |
| |
| return nr_rx; |
| } |
| |
| static void lpc11u6x_uart0_irq_tx_enable(const struct device *dev) |
| { |
| const struct lpc11u6x_uart0_config *cfg = dev->config; |
| |
| cfg->uart0->ier = (cfg->uart0->ier & LPC11U6X_UART0_IER_MASK) | |
| LPC11U6X_UART0_IER_THREINTEN; |
| |
| /* Due to hardware limitations, first TX interrupt is not triggered when |
| * enabling it in the IER register. We have to trigger it. |
| */ |
| NVIC_SetPendingIRQ(DT_INST_IRQN(0)); |
| } |
| |
| static void lpc11u6x_uart0_irq_tx_disable(const struct device *dev) |
| { |
| const struct lpc11u6x_uart0_config *cfg = dev->config; |
| |
| cfg->uart0->ier = (cfg->uart0->ier & LPC11U6X_UART0_IER_MASK) & |
| ~LPC11U6X_UART0_IER_THREINTEN; |
| } |
| |
| static int lpc11u6x_uart0_irq_tx_complete(const struct device *dev) |
| { |
| const struct lpc11u6x_uart0_config *cfg = dev->config; |
| |
| return (cfg->uart0->lsr & LPC11U6X_UART0_LSR_TEMT) != 0; |
| } |
| |
| static int lpc11u6x_uart0_irq_tx_ready(const struct device *dev) |
| { |
| const struct lpc11u6x_uart0_config *cfg = dev->config; |
| |
| return (cfg->uart0->lsr & LPC11U6X_UART0_LSR_THRE) && |
| (cfg->uart0->ier & LPC11U6X_UART0_IER_THREINTEN); |
| } |
| |
| static void lpc11u6x_uart0_irq_rx_enable(const struct device *dev) |
| { |
| const struct lpc11u6x_uart0_config *cfg = dev->config; |
| |
| cfg->uart0->ier = (cfg->uart0->ier & LPC11U6X_UART0_IER_MASK) | |
| LPC11U6X_UART0_IER_RBRINTEN; |
| } |
| |
| static void lpc11u6x_uart0_irq_rx_disable(const struct device *dev) |
| { |
| const struct lpc11u6x_uart0_config *cfg = dev->config; |
| |
| cfg->uart0->ier = (cfg->uart0->ier & LPC11U6X_UART0_IER_MASK) & |
| ~LPC11U6X_UART0_IER_RBRINTEN; |
| } |
| |
| static int lpc11u6x_uart0_irq_rx_ready(const struct device *dev) |
| { |
| struct lpc11u6x_uart0_data *data = dev->data; |
| |
| return (LPC11U6X_UART0_IIR_INTID(data->cached_iir) == |
| LPC11U6X_UART0_IIR_INTID_RDA) || |
| (LPC11U6X_UART0_IIR_INTID(data->cached_iir) == |
| LPC11U6X_UART0_IIR_INTID_CTI); |
| } |
| |
| static void lpc11u6x_uart0_irq_err_enable(const struct device *dev) |
| { |
| const struct lpc11u6x_uart0_config *cfg = dev->config; |
| |
| cfg->uart0->ier = (cfg->uart0->ier & LPC11U6X_UART0_IER_MASK) | |
| LPC11U6X_UART0_IER_RLSINTEN; |
| } |
| |
| static void lpc11u6x_uart0_irq_err_disable(const struct device *dev) |
| { |
| const struct lpc11u6x_uart0_config *cfg = dev->config; |
| |
| cfg->uart0->ier = (cfg->uart0->ier & LPC11U6X_UART0_IER_MASK) & |
| ~LPC11U6X_UART0_IER_RLSINTEN; |
| } |
| |
| static int lpc11u6x_uart0_irq_is_pending(const struct device *dev) |
| { |
| struct lpc11u6x_uart0_data *data = dev->data; |
| |
| return !(data->cached_iir & LPC11U6X_UART0_IIR_STATUS); |
| } |
| |
| static int lpc11u6x_uart0_irq_update(const struct device *dev) |
| { |
| const struct lpc11u6x_uart0_config *cfg = dev->config; |
| struct lpc11u6x_uart0_data *data = dev->data; |
| |
| data->cached_iir = cfg->uart0->iir; |
| return 1; |
| } |
| |
| static void lpc11u6x_uart0_irq_callback_set(const struct device *dev, |
| uart_irq_callback_user_data_t cb, |
| void *user_data) |
| { |
| struct lpc11u6x_uart0_data *data = dev->data; |
| |
| data->cb = cb; |
| data->cb_data = user_data; |
| } |
| |
| static void lpc11u6x_uart0_isr(const struct device *dev) |
| { |
| struct lpc11u6x_uart0_data *data = dev->data; |
| |
| if (data->cb) { |
| data->cb(dev, data->cb_data); |
| } |
| } |
| #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ |
| |
| static int lpc11u6x_uart0_init(const struct device *dev) |
| { |
| const struct lpc11u6x_uart0_config *cfg = dev->config; |
| struct lpc11u6x_uart0_data *data = dev->data; |
| int err; |
| |
| /* Apply default pin control state to select RX and TX pins */ |
| err = pinctrl_apply_state(cfg->pincfg, PINCTRL_STATE_DEFAULT); |
| if (err) { |
| return err; |
| } |
| |
| if (!device_is_ready(cfg->clock_dev)) { |
| return -ENODEV; |
| } |
| |
| clock_control_on(cfg->clock_dev, (clock_control_subsys_t) cfg->clkid); |
| |
| /* Configure baudrate, parity and stop bits */ |
| lpc11u6x_uart0_config_baudrate(cfg->clock_dev, cfg, cfg->baudrate); |
| |
| cfg->uart0->lcr |= LPC11U6X_UART0_LCR_WLS_8BITS; /* 8N1 */ |
| |
| data->baudrate = cfg->baudrate; |
| data->parity = UART_CFG_PARITY_NONE; |
| data->stop_bits = UART_CFG_STOP_BITS_1; |
| data->data_bits = UART_CFG_DATA_BITS_8; |
| data->flow_ctrl = UART_CFG_FLOW_CTRL_NONE; |
| |
| /* Configure FIFO */ |
| cfg->uart0->fcr = LPC11U6X_UART0_FCR_FIFO_EN; |
| |
| #ifdef CONFIG_UART_INTERRUPT_DRIVEN |
| cfg->irq_config_func(dev); |
| #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ |
| return 0; |
| } |
| |
| #ifdef CONFIG_UART_INTERRUPT_DRIVEN |
| static void lpc11u6x_uart0_isr_config(const struct device *dev); |
| #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ |
| |
| PINCTRL_DT_DEFINE(DT_NODELABEL(uart0)); |
| |
| BUILD_ASSERT(DT_PROP(DT_NODELABEL(uart0), rx_invert) == 0, |
| "rx-invert not supported for UART0"); |
| BUILD_ASSERT(DT_PROP(DT_NODELABEL(uart0), tx_invert) == 0, |
| "tx-invert not supported for UART0"); |
| |
| static const struct lpc11u6x_uart0_config uart0_config = { |
| .uart0 = (struct lpc11u6x_uart0_regs *) |
| DT_REG_ADDR(DT_NODELABEL(uart0)), |
| .clock_dev = DEVICE_DT_GET(DT_CLOCKS_CTLR(DT_NODELABEL(uart0))), |
| .pincfg = PINCTRL_DT_DEV_CONFIG_GET(DT_NODELABEL(uart0)), |
| .clkid = DT_PHA_BY_IDX(DT_NODELABEL(uart0), clocks, 0, clkid), |
| .baudrate = DT_PROP(DT_NODELABEL(uart0), current_speed), |
| #ifdef CONFIG_UART_INTERRUPT_DRIVEN |
| .irq_config_func = lpc11u6x_uart0_isr_config, |
| #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ |
| }; |
| |
| static const struct uart_driver_api uart0_api = { |
| .poll_in = lpc11u6x_uart0_poll_in, |
| .poll_out = lpc11u6x_uart0_poll_out, |
| .err_check = lpc11u6x_uart0_err_check, |
| #ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE |
| .configure = lpc11u6x_uart0_configure, |
| .config_get = lpc11u6x_uart0_config_get, |
| #endif |
| #ifdef CONFIG_UART_INTERRUPT_DRIVEN |
| .fifo_fill = lpc11u6x_uart0_fifo_fill, |
| .fifo_read = lpc11u6x_uart0_fifo_read, |
| .irq_tx_enable = lpc11u6x_uart0_irq_tx_enable, |
| .irq_tx_disable = lpc11u6x_uart0_irq_tx_disable, |
| .irq_tx_ready = lpc11u6x_uart0_irq_tx_ready, |
| .irq_tx_complete = lpc11u6x_uart0_irq_tx_complete, |
| .irq_rx_enable = lpc11u6x_uart0_irq_rx_enable, |
| .irq_rx_disable = lpc11u6x_uart0_irq_rx_disable, |
| .irq_rx_ready = lpc11u6x_uart0_irq_rx_ready, |
| .irq_err_enable = lpc11u6x_uart0_irq_err_enable, |
| .irq_err_disable = lpc11u6x_uart0_irq_err_disable, |
| .irq_is_pending = lpc11u6x_uart0_irq_is_pending, |
| .irq_update = lpc11u6x_uart0_irq_update, |
| .irq_callback_set = lpc11u6x_uart0_irq_callback_set, |
| #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ |
| }; |
| |
| static struct lpc11u6x_uart0_data uart0_data; |
| |
| DEVICE_DT_DEFINE(DT_NODELABEL(uart0), |
| &lpc11u6x_uart0_init, |
| NULL, |
| &uart0_data, &uart0_config, |
| PRE_KERNEL_1, CONFIG_SERIAL_INIT_PRIORITY, |
| &uart0_api); |
| |
| #ifdef CONFIG_UART_INTERRUPT_DRIVEN |
| static void lpc11u6x_uart0_isr_config(const struct device *dev) |
| { |
| IRQ_CONNECT(DT_IRQN(DT_NODELABEL(uart0)), |
| DT_IRQ(DT_NODELABEL(uart0), priority), |
| lpc11u6x_uart0_isr, DEVICE_DT_GET(DT_NODELABEL(uart0)), 0); |
| |
| irq_enable(DT_IRQN(DT_NODELABEL(uart0))); |
| } |
| #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ |
| |
| #endif /* DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart0)) */ |
| |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart1)) || \ |
| DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart2)) || \ |
| DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart3)) || \ |
| DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart4)) |
| |
| static int lpc11u6x_uartx_poll_in(const struct device *dev, unsigned char *c) |
| { |
| const struct lpc11u6x_uartx_config *cfg = dev->config; |
| |
| if (!(cfg->base->stat & LPC11U6X_UARTX_STAT_RXRDY)) { |
| return -1; |
| } |
| *c = cfg->base->rx_dat; |
| return 0; |
| } |
| |
| static void lpc11u6x_uartx_poll_out(const struct device *dev, unsigned char c) |
| { |
| const struct lpc11u6x_uartx_config *cfg = dev->config; |
| |
| while (!(cfg->base->stat & LPC11U6X_UARTX_STAT_TXRDY)) { |
| } |
| cfg->base->tx_dat = c; |
| } |
| |
| static int lpc11u6x_uartx_err_check(const struct device *dev) |
| { |
| const struct lpc11u6x_uartx_config *cfg = dev->config; |
| int ret = 0; |
| |
| if (cfg->base->stat & LPC11U6X_UARTX_STAT_OVERRUNINT) { |
| ret |= UART_ERROR_OVERRUN; |
| } |
| if (cfg->base->stat & LPC11U6X_UARTX_STAT_FRAMERRINT) { |
| ret |= UART_ERROR_FRAMING; |
| } |
| if (cfg->base->stat & LPC11U6X_UARTX_STAT_PARITYERRINT) { |
| ret |= UART_ERROR_PARITY; |
| } |
| |
| return ret; |
| } |
| |
| static void lpc11u6x_uartx_config_baud(const struct lpc11u6x_uartx_config *cfg, |
| uint32_t baudrate) |
| { |
| uint32_t clk_rate; |
| uint32_t div; |
| const struct device *clk_drv = cfg->clock_dev; |
| |
| clock_control_get_rate(clk_drv, (clock_control_subsys_t) cfg->clkid, |
| &clk_rate); |
| |
| div = clk_rate / (16 * baudrate); |
| if (div != 0) { |
| div -= 1; |
| } |
| cfg->base->brg = div & LPC11U6X_UARTX_BRG_MASK; |
| } |
| |
| #ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE |
| static int lpc11u6x_uartx_configure(const struct device *dev, |
| const struct uart_config *cfg) |
| { |
| const struct lpc11u6x_uartx_config *dev_cfg = dev->config; |
| struct lpc11u6x_uartx_data *data = dev->data; |
| uint32_t flags = 0; |
| |
| /* We only support baudrates that are multiple of 9600 */ |
| if (cfg->baudrate % 9600) { |
| return -ENOTSUP; |
| } |
| |
| switch (cfg->parity) { |
| case UART_CFG_PARITY_NONE: |
| flags |= LPC11U6X_UARTX_CFG_PARITY_NONE; |
| break; |
| case UART_CFG_PARITY_ODD: |
| flags |= LPC11U6X_UARTX_CFG_PARITY_ODD; |
| break; |
| case UART_CFG_PARITY_EVEN: |
| flags |= LPC11U6X_UARTX_CFG_PARITY_EVEN; |
| break; |
| case UART_CFG_PARITY_MARK: |
| __fallthrough; |
| case UART_CFG_PARITY_SPACE: |
| return -ENOTSUP; |
| default: |
| return -EINVAL; |
| } |
| |
| switch (cfg->stop_bits) { |
| case UART_CFG_STOP_BITS_0_5: |
| return -ENOTSUP; |
| case UART_CFG_STOP_BITS_1: |
| flags |= LPC11U6X_UARTX_CFG_STOP_1BIT; |
| break; |
| case UART_CFG_STOP_BITS_1_5: |
| return -ENOTSUP; |
| case UART_CFG_STOP_BITS_2: |
| flags |= LPC11U6X_UARTX_CFG_STOP_2BIT; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| switch (cfg->data_bits) { |
| case UART_CFG_DATA_BITS_5: |
| __fallthrough; |
| case UART_CFG_DATA_BITS_6: |
| return -ENOTSUP; |
| case UART_CFG_DATA_BITS_7: |
| flags |= LPC11U6X_UARTX_CFG_DATALEN_7BIT; |
| break; |
| case UART_CFG_DATA_BITS_8: |
| flags |= LPC11U6X_UARTX_CFG_DATALEN_8BIT; |
| break; |
| case UART_CFG_DATA_BITS_9: |
| flags |= LPC11U6X_UARTX_CFG_DATALEN_9BIT; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| if (cfg->flow_ctrl != UART_CFG_FLOW_CTRL_NONE) { |
| return -ENOTSUP; |
| } |
| |
| if (dev_cfg->rx_invert) { |
| flags |= LPC11U6X_UARTX_CFG_RXPOL(1); |
| } |
| if (dev_cfg->tx_invert) { |
| flags |= LPC11U6X_UARTX_CFG_TXPOL(1); |
| } |
| |
| /* Disable UART */ |
| dev_cfg->base->cfg = 0; |
| |
| /* Update baudrate */ |
| lpc11u6x_uartx_config_baud(dev_cfg, cfg->baudrate); |
| |
| /* Set parity, data bits, stop bits and re-enable UART interface */ |
| dev_cfg->base->cfg = flags | LPC11U6X_UARTX_CFG_ENABLE; |
| |
| data->baudrate = cfg->baudrate; |
| data->parity = cfg->parity; |
| data->stop_bits = cfg->stop_bits; |
| data->data_bits = cfg->data_bits; |
| data->flow_ctrl = cfg->flow_ctrl; |
| |
| return 0; |
| } |
| |
| static int lpc11u6x_uartx_config_get(const struct device *dev, |
| struct uart_config *cfg) |
| { |
| const struct lpc11u6x_uartx_data *data = dev->data; |
| |
| cfg->baudrate = data->baudrate; |
| cfg->parity = data->parity; |
| cfg->stop_bits = data->stop_bits; |
| cfg->data_bits = data->data_bits; |
| cfg->flow_ctrl = data->flow_ctrl; |
| |
| return 0; |
| } |
| #endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */ |
| |
| #ifdef CONFIG_UART_INTERRUPT_DRIVEN |
| static int lpc11u6x_uartx_fifo_fill(const struct device *dev, |
| const uint8_t *data, |
| int size) |
| { |
| const struct lpc11u6x_uartx_config *cfg = dev->config; |
| int tx_size = 0; |
| |
| while (tx_size < size && |
| (cfg->base->stat & LPC11U6X_UARTX_STAT_TXRDY)) { |
| cfg->base->tx_dat = data[tx_size++]; |
| } |
| return tx_size; |
| } |
| |
| static int lpc11u6x_uartx_fifo_read(const struct device *dev, uint8_t *data, |
| int size) |
| { |
| const struct lpc11u6x_uartx_config *cfg = dev->config; |
| int rx_size = 0; |
| |
| while (rx_size < size && |
| (cfg->base->stat & LPC11U6X_UARTX_STAT_RXRDY)) { |
| data[rx_size++] = cfg->base->rx_dat; |
| } |
| return rx_size; |
| } |
| |
| static void lpc11u6x_uartx_irq_tx_enable(const struct device *dev) |
| { |
| const struct lpc11u6x_uartx_config *cfg = dev->config; |
| |
| cfg->base->int_en_set = (cfg->base->int_en_set & |
| LPC11U6X_UARTX_INT_EN_SET_MASK) | |
| LPC11U6X_UARTX_INT_EN_SET_TXRDYEN; |
| } |
| |
| static void lpc11u6x_uartx_irq_tx_disable(const struct device *dev) |
| { |
| const struct lpc11u6x_uartx_config *cfg = dev->config; |
| |
| cfg->base->int_en_clr = LPC11U6X_UARTX_INT_EN_CLR_TXRDYCLR; |
| } |
| |
| static int lpc11u6x_uartx_irq_tx_ready(const struct device *dev) |
| { |
| const struct lpc11u6x_uartx_config *cfg = dev->config; |
| |
| return (cfg->base->stat & LPC11U6X_UARTX_STAT_TXRDY) && |
| (cfg->base->int_en_set & LPC11U6X_UARTX_INT_EN_SET_TXRDYEN); |
| } |
| |
| static int lpc11u6x_uartx_irq_tx_complete(const struct device *dev) |
| { |
| const struct lpc11u6x_uartx_config *cfg = dev->config; |
| |
| return (cfg->base->stat & LPC11U6X_UARTX_STAT_TXIDLE) != 0; |
| } |
| |
| static void lpc11u6x_uartx_irq_rx_enable(const struct device *dev) |
| { |
| const struct lpc11u6x_uartx_config *cfg = dev->config; |
| |
| cfg->base->int_en_set = (cfg->base->int_en_set & |
| LPC11U6X_UARTX_INT_EN_SET_MASK) | |
| LPC11U6X_UARTX_INT_EN_SET_RXRDYEN; |
| } |
| |
| static void lpc11u6x_uartx_irq_rx_disable(const struct device *dev) |
| { |
| const struct lpc11u6x_uartx_config *cfg = dev->config; |
| |
| cfg->base->int_en_clr = LPC11U6X_UARTX_INT_EN_CLR_RXRDYCLR; |
| } |
| |
| static int lpc11u6x_uartx_irq_rx_ready(const struct device *dev) |
| { |
| const struct lpc11u6x_uartx_config *cfg = dev->config; |
| |
| return (cfg->base->stat & LPC11U6X_UARTX_STAT_RXRDY) && |
| (cfg->base->int_en_set & LPC11U6X_UARTX_INT_EN_SET_RXRDYEN); |
| } |
| |
| static void lpc11u6x_uartx_irq_err_enable(const struct device *dev) |
| { |
| const struct lpc11u6x_uartx_config *cfg = dev->config; |
| |
| cfg->base->int_en_set = (cfg->base->int_en_set & |
| LPC11U6X_UARTX_INT_EN_SET_MASK) | |
| (LPC11U6X_UARTX_INT_EN_SET_RXRDYEN | |
| LPC11U6X_UARTX_INT_EN_SET_FRAMERREN | |
| LPC11U6X_UARTX_INT_EN_SET_PARITYERREN); |
| } |
| |
| static void lpc11u6x_uartx_irq_err_disable(const struct device *dev) |
| { |
| const struct lpc11u6x_uartx_config *cfg = dev->config; |
| |
| cfg->base->int_en_clr = LPC11U6X_UARTX_INT_EN_CLR_OVERRUNCLR | |
| LPC11U6X_UARTX_INT_EN_CLR_FRAMERRCLR | |
| LPC11U6X_UARTX_INT_EN_CLR_PARITYERRCLR; |
| } |
| |
| static int lpc11u6x_uartx_irq_is_pending(const struct device *dev) |
| { |
| const struct lpc11u6x_uartx_config *cfg = dev->config; |
| |
| if ((cfg->base->stat & LPC11U6X_UARTX_STAT_RXRDY) && |
| (cfg->base->int_stat & LPC11U6X_UARTX_INT_STAT_RXRDY)) { |
| return 1; |
| } |
| if ((cfg->base->stat & LPC11U6X_UARTX_STAT_TXRDY) && |
| cfg->base->int_stat & LPC11U6X_UARTX_INT_STAT_TXRDY) { |
| return 1; |
| } |
| if (cfg->base->stat & (LPC11U6X_UARTX_STAT_OVERRUNINT | |
| LPC11U6X_UARTX_STAT_FRAMERRINT | |
| LPC11U6X_UARTX_STAT_PARITYERRINT)) { |
| return 1; |
| } |
| return 0; |
| } |
| |
| static int lpc11u6x_uartx_irq_update(const struct device *dev) |
| { |
| return 1; |
| } |
| |
| static void lpc11u6x_uartx_irq_callback_set(const struct device *dev, |
| uart_irq_callback_user_data_t cb, |
| void *user_data) |
| { |
| struct lpc11u6x_uartx_data *data = dev->data; |
| |
| data->cb = cb; |
| data->cb_data = user_data; |
| } |
| |
| static void lpc11u6x_uartx_isr(const struct device *dev) |
| { |
| struct lpc11u6x_uartx_data *data = dev->data; |
| |
| if (data->cb) { |
| data->cb(dev, data->cb_data); |
| } |
| } |
| |
| static void lpc11u6x_uartx_shared_isr(const void *arg) |
| { |
| struct lpc11u6x_uartx_shared_irq *shared_irq = |
| (struct lpc11u6x_uartx_shared_irq *)arg; |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(shared_irq->devices); i++) { |
| if (shared_irq->devices[i]) { |
| lpc11u6x_uartx_isr(shared_irq->devices[i]); |
| } |
| } |
| } |
| #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ |
| |
| static int lpc11u6x_uartx_init(const struct device *dev) |
| { |
| const struct lpc11u6x_uartx_config *cfg = dev->config; |
| struct lpc11u6x_uartx_data *data = dev->data; |
| int err; |
| |
| /* Apply default pin control state to select RX and TX pins */ |
| err = pinctrl_apply_state(cfg->pincfg, PINCTRL_STATE_DEFAULT); |
| if (err) { |
| return err; |
| } |
| |
| clock_control_on(cfg->clock_dev, (clock_control_subsys_t) cfg->clkid); |
| |
| /* Configure baudrate, parity and stop bits */ |
| lpc11u6x_uartx_config_baud(cfg, cfg->baudrate); |
| cfg->base->cfg = LPC11U6X_UARTX_CFG_DATALEN_8BIT; /* 8N1 */ |
| |
| data->baudrate = cfg->baudrate; |
| data->parity = UART_CFG_PARITY_NONE; |
| data->stop_bits = UART_CFG_STOP_BITS_1; |
| data->data_bits = UART_CFG_DATA_BITS_8; |
| data->flow_ctrl = UART_CFG_FLOW_CTRL_NONE; |
| |
| if (cfg->rx_invert) { |
| cfg->base->cfg |= LPC11U6X_UARTX_CFG_RXPOL(1); |
| } |
| if (cfg->tx_invert) { |
| cfg->base->cfg |= LPC11U6X_UARTX_CFG_TXPOL(1); |
| } |
| |
| /* Enable UART */ |
| cfg->base->cfg = (cfg->base->cfg & LPC11U6X_UARTX_CFG_MASK) | |
| LPC11U6X_UARTX_CFG_ENABLE; |
| |
| #ifdef CONFIG_UART_INTERRUPT_DRIVEN |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart1)) || \ |
| DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart4)) |
| lpc11u6x_uartx_isr_config_1(dev); |
| #endif /* DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart1)) || |
| * DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart4)) |
| */ |
| |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart2)) || \ |
| DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart3)) |
| lpc11u6x_uartx_isr_config_2(dev); |
| #endif /* DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart2)) || |
| * DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart3)) |
| */ |
| #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ |
| return 0; |
| } |
| |
| static const struct uart_driver_api uartx_api = { |
| .poll_in = lpc11u6x_uartx_poll_in, |
| .poll_out = lpc11u6x_uartx_poll_out, |
| .err_check = lpc11u6x_uartx_err_check, |
| #ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE |
| .configure = lpc11u6x_uartx_configure, |
| .config_get = lpc11u6x_uartx_config_get, |
| #endif |
| #ifdef CONFIG_UART_INTERRUPT_DRIVEN |
| .fifo_fill = lpc11u6x_uartx_fifo_fill, |
| .fifo_read = lpc11u6x_uartx_fifo_read, |
| .irq_tx_enable = lpc11u6x_uartx_irq_tx_enable, |
| .irq_tx_disable = lpc11u6x_uartx_irq_tx_disable, |
| .irq_tx_ready = lpc11u6x_uartx_irq_tx_ready, |
| .irq_tx_complete = lpc11u6x_uartx_irq_tx_complete, |
| .irq_rx_enable = lpc11u6x_uartx_irq_rx_enable, |
| .irq_rx_disable = lpc11u6x_uartx_irq_rx_disable, |
| .irq_rx_ready = lpc11u6x_uartx_irq_rx_ready, |
| .irq_err_enable = lpc11u6x_uartx_irq_err_enable, |
| .irq_err_disable = lpc11u6x_uartx_irq_err_disable, |
| .irq_is_pending = lpc11u6x_uartx_irq_is_pending, |
| .irq_update = lpc11u6x_uartx_irq_update, |
| .irq_callback_set = lpc11u6x_uartx_irq_callback_set, |
| #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ |
| }; |
| |
| |
| #define LPC11U6X_UARTX_INIT(idx) \ |
| PINCTRL_DT_DEFINE(DT_NODELABEL(uart##idx)); \ |
| \ |
| static const struct lpc11u6x_uartx_config uart_cfg_##idx = { \ |
| .base = (struct lpc11u6x_uartx_regs *) \ |
| DT_REG_ADDR(DT_NODELABEL(uart##idx)), \ |
| .clock_dev = DEVICE_DT_GET(DT_CLOCKS_CTLR(DT_NODELABEL(uart##idx))), \ |
| .clkid = DT_PHA_BY_IDX(DT_NODELABEL(uart##idx), clocks, 0, clkid), \ |
| .pincfg = PINCTRL_DT_DEV_CONFIG_GET(DT_NODELABEL(uart##idx)), \ |
| .baudrate = DT_PROP(DT_NODELABEL(uart##idx), current_speed), \ |
| .rx_invert = DT_PROP(DT_NODELABEL(uart##idx), rx_invert), \ |
| .tx_invert = DT_PROP(DT_NODELABEL(uart##idx), tx_invert), \ |
| }; \ |
| \ |
| static struct lpc11u6x_uartx_data uart_data_##idx; \ |
| \ |
| DEVICE_DT_DEFINE(DT_NODELABEL(uart##idx), \ |
| &lpc11u6x_uartx_init, NULL, \ |
| &uart_data_##idx, &uart_cfg_##idx, \ |
| PRE_KERNEL_1, CONFIG_SERIAL_INIT_PRIORITY, \ |
| &uartx_api) |
| |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart1)) |
| LPC11U6X_UARTX_INIT(1); |
| #endif /* DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart1)) */ |
| |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart2)) |
| LPC11U6X_UARTX_INIT(2); |
| #endif /* DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart2)) */ |
| |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart3)) |
| LPC11U6X_UARTX_INIT(3); |
| #endif /* DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart3)) */ |
| |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart4)) |
| LPC11U6X_UARTX_INIT(4); |
| #endif /* DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart4)) */ |
| |
| #if CONFIG_UART_INTERRUPT_DRIVEN && \ |
| (DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart1)) || \ |
| DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart4))) |
| |
| struct lpc11u6x_uartx_shared_irq lpc11u6x_uartx_shared_irq_info_1 = { |
| .devices = { |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart1)) |
| DEVICE_DT_GET(DT_NODELABEL(uart1)), |
| #else |
| NULL, |
| #endif /* DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart1)) */ |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart4)) |
| DEVICE_DT_GET(DT_NODELABEL(uart4)), |
| #else |
| NULL, |
| #endif /* DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart4)) */ |
| }, |
| }; |
| |
| static void lpc11u6x_uartx_isr_config_1(const struct device *dev) |
| { |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart1)) |
| IRQ_CONNECT(DT_IRQN(DT_NODELABEL(uart1)), |
| DT_IRQ(DT_NODELABEL(uart1), priority), |
| lpc11u6x_uartx_shared_isr, |
| &lpc11u6x_uartx_shared_irq_info_1, |
| 0); |
| irq_enable(DT_IRQN(DT_NODELABEL(uart1))); |
| #else |
| IRQ_CONNECT(DT_IRQN(DT_NODELABEL(uart4)), |
| DT_IRQ(DT_NODELABEL(uart4), priority), |
| lpc11u6x_uartx_shared_isr, |
| &lpc11u6x_uartx_shared_irq_info_1, |
| 0); |
| irq_enable(DT_IRQN(DT_NODELABEL(uart4))); |
| #endif /* DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart1)) */ |
| } |
| #endif /* CONFIG_UART_INTERRUPT_DRIVEN && |
| * (DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart1)) || |
| * DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart4))) |
| */ |
| |
| #if CONFIG_UART_INTERRUPT_DRIVEN && \ |
| (DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart2)) || \ |
| DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart3))) |
| struct lpc11u6x_uartx_shared_irq lpc11u6x_uartx_shared_irq_info_2 = { |
| .devices = { |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart2)) |
| DEVICE_DT_GET(DT_NODELABEL(uart2)), |
| #else |
| NULL, |
| #endif /* DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart2)) */ |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart3)) |
| DEVICE_DT_GET(DT_NODELABEL(uart3)), |
| #else |
| NULL, |
| #endif /* DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart3)) */ |
| }, |
| }; |
| |
| static void lpc11u6x_uartx_isr_config_2(const struct device *dev) |
| { |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart2)) |
| IRQ_CONNECT(DT_IRQN(DT_NODELABEL(uart2)), |
| DT_IRQ(DT_NODELABEL(uart2), priority), |
| lpc11u6x_uartx_shared_isr, |
| &lpc11u6x_uartx_shared_irq_info_2, |
| 0); |
| irq_enable(DT_IRQN(DT_NODELABEL(uart2))); |
| #else |
| IRQ_CONNECT(DT_IRQN(DT_NODELABEL(uart3)), |
| DT_IRQ(DT_NODELABEL(uart3), priority), |
| lpc11u6x_uartx_shared_isr, |
| &lpc11u6x_uartx_shared_irq_info_2, |
| 0); |
| irq_enable(DT_IRQN(DT_NODELABEL(uart3))); |
| #endif /* DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart2)) */ |
| } |
| #endif /* CONFIG_UART_INTERRUPT_DRIVEN && |
| * (DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart2)) || |
| * DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(uart3))) |
| */ |
| #endif /* DT_NODE_EXISTS(DT_NODELABEL(uart1) || |
| * DT_NODE_EXISTS(DT_NODELABEL(uart2) || |
| * DT_NODE_EXISTS(DT_NODELABEL(uart3) || |
| * DT_NODE_EXISTS(DT_NODELABEL(uart4) |
| */ |