nt9856x/BSP/linux-kernel/drivers/tty/serial/nvt_serial.c
2023-03-28 15:07:53 +08:00

1447 lines
38 KiB
C
Executable File

// SPDX-License-Identifier: GPL-2.0
/*
* NVT UART
*
* Driver for NVT Soc
*
* @file nvt_serial.c
*
* Copyright Novatek Microelectronics Corp. 2020. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#if defined(CONFIG_SERIAL_NVT_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)
#define SUPPORT_SYSRQ
#endif
#include <linux/atomic.h>
#include <linux/hrtimer.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/irq.h>
#include <linux/init.h>
#include <linux/console.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial_core.h>
#include <linux/serial.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_irq.h>
#include <linux/dma-mapping.h>
#include <linux/clk-provider.h>
#include <plat/nvt_serial.h>
#include <plat/top.h>
#define DRIVER_VERSION "2.0.6"
#define DRIVER_NAME "nvt_serial"
#define RX_DMA_ADDR_ALIGN 16
static int global_line = 0;
static int __serial_rx_size = -1;
module_param_named(serial_rx_size, __serial_rx_size, int, 0600);
static int __serial_tx_dma_en_debug = -1;
module_param_named(serial_tx_dma_en_debug, __serial_tx_dma_en_debug, int, 0600);
static int __serial_rx_dma_en_debug = -1;
module_param_named(serial_rx_dma_en_debug, __serial_rx_dma_en_debug, int, 0600);
static int __serial_debug = 0;
module_param_named(serial_debug, __serial_debug, int, 0600);
static int __serial_debug_console = 0;
module_param_named(serial_debug_console, __serial_debug_console, int, 0600);
static int __serial_flow_debug = 0;
module_param_named(serial_flow_debug, __serial_flow_debug, int, 0600);
#define serial_dbg(fmt, ...) do { \
if (port->line != 0 || __serial_debug_console) { \
if (__serial_debug) \
pr_info("nvt_serial%d: "fmt, port->line, ##__VA_ARGS__); \
else \
pr_debug("nvt_serial%d: "fmt, port->line,##__VA_ARGS__); \
} \
} while (0)
#define serial_err(fmt, ...) do { \
pr_err("nvt_serial%d: "fmt, port->line, ##__VA_ARGS__); \
} while (0)
#define serial_flow_dbg(fmt, ...) do { \
if (port->line != 0 || __serial_debug_console) { \
if (__serial_flow_debug) \
pr_info("nvt_serial%d: %s\n"fmt, port->line, __func__, ##__VA_ARGS__); \
} \
} while (0)
#define UART_TO_NVT(uart_port) ((struct nvt_port *) uart_port)
static unsigned int nvt_read(struct uart_port *port, unsigned int off)
{
return readl_relaxed(port->membase + off);
}
static void nvt_write(struct uart_port *port, unsigned int off, unsigned int val)
{
struct nvt_port *nvt_port = container_of(port, struct nvt_port, uart);
unsigned long flags = 0;
spin_lock_irqsave(&nvt_port->write_lock, flags);
writel_relaxed(val, port->membase + off);
spin_unlock_irqrestore(&nvt_port->write_lock, flags);
}
static void nvt_masked_write(struct uart_port *port, unsigned int off, unsigned int mask, unsigned int val)
{
struct nvt_port *nvt_port = container_of(port, struct nvt_port, uart);
unsigned long flags = 0;
unsigned int tmp = nvt_read(port, off); /* be careful that some registers are cleared after reading, such as RBR */
spin_lock_irqsave(&nvt_port->write_lock, flags);
tmp &= ~mask;
tmp |= val & mask;
writel_relaxed(tmp, port->membase + off);
spin_unlock_irqrestore(&nvt_port->write_lock, flags);
}
static DEFINE_SPINLOCK(write_lock_early);
static void nvt_write_early(struct uart_port *port, unsigned int off, unsigned int val)
{
unsigned long flags = 0;
spin_lock_irqsave(&write_lock_early, flags);
writel_relaxed(val, port->membase + off);
spin_unlock_irqrestore(&write_lock_early, flags);
}
static void nvt_clear_fifos(struct uart_port *port)
{
struct circ_buf *xmit;
xmit = &port->state->xmit;
/* Enable FIFO and Reset */
nvt_write(port, UART_FCR_REG, FIFO_EN_BIT | RX_FIFO_RESET_BIT | TX_FIFO_RESET_BIT);
/* Disable FIFO */
nvt_write(port, UART_FCR_REG, 0);
if(xmit->head != xmit->tail){
serial_dbg("\nFIFO remain %d\n",CIRC_CNT_TO_END(xmit->head, xmit->tail, UART_XMIT_SIZE));
/* Force reset FIFO buffer */
xmit->head = xmit->tail;
}
}
static void nvt_enable_rx_dma(struct work_struct *work)
{
struct nvt_port *nvt_port = container_of(work, struct nvt_port, rx_dma_work);
struct uart_port *port = &nvt_port->uart;
serial_flow_dbg();
if (__serial_rx_size > 0)
nvt_port->rx_size = __serial_rx_size;
else
nvt_port->rx_size = UART_XMIT_SIZE;
nvt_write(port, UART_RX_DMA_ADDR_REG, nvt_port->rx_dma_addr);
nvt_write(port, UART_RX_DMA_SIZE_REG, nvt_port->rx_size);
nvt_masked_write(port, UART_DMA_INT_CTRL_REG, RX_DMA_DONE_INTEN_BIT, RX_DMA_DONE_INTEN_BIT);
nvt_masked_write(port, UART_DMA_INT_CTRL_REG, RX_DMA_ERR_INTEN_BIT, RX_DMA_ERR_INTEN_BIT);
nvt_masked_write(port, UART_RX_DMA_CTRL_REG, RX_DMA_EN_BIT, RX_DMA_EN_BIT);
}
static void nvt_enable_tx_dma(struct uart_port *port)
{
struct nvt_port *nvt_port = UART_TO_NVT(port);
struct circ_buf *xmit = &port->state->xmit;
serial_flow_dbg();
nvt_port->tx_size = 0;
while (!uart_circ_empty(xmit)) {
*(nvt_port->tx_virt_addr + nvt_port->tx_size) = xmit->buf[xmit->tail];
port->icount.tx++;
serial_dbg("head(%d) tail(%d) cnt_tx(%d), tx_virt_addr[%d](0x%x)\n", xmit->head, xmit->tail, port->icount.tx, nvt_port->tx_size, *(nvt_port->tx_virt_addr + nvt_port->tx_size));
xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
nvt_port->tx_size++;
}
if (nvt_port->tx_size > 0) {
nvt_write(port, UART_TX_DMA_ADDR_REG, nvt_port->tx_dma_addr);
nvt_write(port, UART_TX_DMA_SIZE_REG, nvt_port->tx_size);
nvt_masked_write(port, UART_DMA_INT_CTRL_REG, TX_DMA_DONE_INTEN_BIT, TX_DMA_DONE_INTEN_BIT);
nvt_masked_write(port, UART_TX_DMA_CTRL_REG, TX_DMA_EN_BIT, TX_DMA_EN_BIT);
}
}
static void nvt_handle_rx_dma(struct uart_port *port, unsigned int lsr_r)
{
struct nvt_port *nvt_port = UART_TO_NVT(port);
struct tty_port *tport = &port->state->port;
unsigned char ch;
char flag;
unsigned int idx, dma_transfered;
serial_flow_dbg();
dma_transfered = (nvt_read(port, UART_RX_CUR_ADDR_REG) & RX_CUR_ADDRESS_MASK) - nvt_port->rx_dma_addr;
for (idx = 0; idx < dma_transfered; idx++) {
ch = *(nvt_port->rx_virt_addr + idx);
flag = TTY_NORMAL;
port->icount.rx++;
serial_dbg("start(0x%x) end(0x%x) cnt_rx(%d), rx_virt_addr[%d](0x%x)\n",
(unsigned int)nvt_port->rx_virt_addr, (unsigned int)nvt_port->rx_virt_addr + dma_transfered, port->icount.rx, idx, ch);
lsr_r |= nvt_port->lsr_break_flag;
nvt_port->lsr_break_flag = 0;
if (lsr_r & BREAK_INT_BIT) {
port->icount.brk++;
if (uart_handle_break(port))
continue;
} else if (lsr_r & PARITY_ERR_BIT)
port->icount.parity++;
else if (lsr_r & FRAMING_ERR_BIT)
port->icount.frame++;
if (lsr_r & OVERRUN_ERR_BIT)
port->icount.overrun++;
/*
* Mask off conditions which should be ignored.
*/
lsr_r &= port->read_status_mask;
if (lsr_r & BREAK_INT_BIT)
flag = TTY_BREAK;
else if (lsr_r & PARITY_ERR_BIT)
flag = TTY_PARITY;
else if (lsr_r & FRAMING_ERR_BIT)
flag = TTY_FRAME;
if (!(uart_handle_sysrq_char(port, ch)))
uart_insert_char(port, lsr_r, OVERRUN_ERR_BIT, ch, flag);
lsr_r = nvt_read(port, UART_LSR_REG);
}
/*
* Drop the lock here since it might end up calling
* uart_start(), which takes the lock.
*/
spin_unlock(&port->lock);
tty_flip_buffer_push(tport);
spin_lock(&port->lock);
}
static void nvt_flush_rx(struct uart_port *port)
{
struct nvt_port *nvt_port = UART_TO_NVT(port);
struct tty_port *tport = &port->state->port;
unsigned char ch;
char flag;
unsigned int lsr_r;
serial_flow_dbg();
while ((lsr_r = nvt_read(port, UART_LSR_REG)) & DATA_READY_BIT) {
ch = nvt_read(port, UART_RBR_REG);
flag = TTY_NORMAL;
port->icount.rx++;
serial_dbg("LSR(0x%x) cnt_rx(%d), read ch(0x%x) from RBR\n", lsr_r, port->icount.rx, ch);
lsr_r |= nvt_port->lsr_break_flag;
nvt_port->lsr_break_flag = 0;
if (lsr_r & BREAK_INT_BIT) {
port->icount.brk++;
if (uart_handle_break(port))
continue;
} else if (lsr_r & PARITY_ERR_BIT)
port->icount.parity++;
else if (lsr_r & FRAMING_ERR_BIT)
port->icount.frame++;
if (lsr_r & OVERRUN_ERR_BIT)
port->icount.overrun++;
/*
* Mask off conditions which should be ignored.
*/
lsr_r &= port->read_status_mask;
if (lsr_r & BREAK_INT_BIT)
flag = TTY_BREAK;
else if (lsr_r & PARITY_ERR_BIT)
flag = TTY_PARITY;
else if (lsr_r & FRAMING_ERR_BIT)
flag = TTY_FRAME;
if (!(uart_handle_sysrq_char(port, ch)))
uart_insert_char(port, lsr_r, OVERRUN_ERR_BIT, ch, flag);
}
/*
* Drop the lock here since it might end up calling
* uart_start(), which takes the lock.
*/
spin_unlock(&port->lock);
tty_flip_buffer_push(tport);
spin_lock(&port->lock);
}
static void nvt_stop_rx(struct uart_port *port)
{
struct nvt_port *nvt_port = UART_TO_NVT(port);
serial_flow_dbg();
if (nvt_port->rx_dma_en && port->line != 0) {
nvt_masked_write(port, UART_DMA_INT_CTRL_REG, RX_DMA_DONE_INTEN_BIT, 0);
nvt_masked_write(port, UART_DMA_INT_CTRL_REG, RX_DMA_ERR_INTEN_BIT, 0);
if (nvt_read(port, UART_RX_DMA_CTRL_REG) & RX_DMA_EN_BIT) {
nvt_handle_rx_dma(port, nvt_read(port, UART_LSR_REG));
}
nvt_masked_write(port, UART_RX_DMA_CTRL_REG, RX_DMA_EN_BIT, 0);
while (nvt_read(port, UART_RX_DMA_CTRL_REG) & RX_DMA_EN_BIT) {
;
}
nvt_flush_rx(port);
}
nvt_masked_write(port, UART_IER_REG, RLS_INTEN_BIT | RDA_INTEN_BIT, 0);
}
static void nvt_stop_tx(struct uart_port *port)
{
struct nvt_port *nvt_port = UART_TO_NVT(port);
serial_flow_dbg();
if (nvt_port->tx_dma_en && port->line != 0) {
nvt_masked_write(port, UART_DMA_INT_CTRL_REG, TX_DMA_DONE_INTEN_BIT, 0);
nvt_masked_write(port, UART_TX_DMA_CTRL_REG, TX_DMA_EN_BIT, 0);
while (nvt_read(port, UART_TX_DMA_CTRL_REG) & TX_DMA_EN_BIT) {
;
}
}
nvt_masked_write(port, UART_IER_REG, THR_EMPTY_INTEN_BIT, 0);
while (nvt_read(port, UART_IER_REG) & THR_EMPTY_INTEN_BIT) {
;
}
}
#define UART_LSR_BRK_ERROR_BITS 0x1E /* BI, FE, PE, OE bits */
#define LSR_SAVE_FLAGS UART_LSR_BRK_ERROR_BITS
static void nvt_handle_tx(struct uart_port *port);
static void nvt_start_tx(struct uart_port *port)
{
struct nvt_port *nvt_port = UART_TO_NVT(port);
unsigned char lsr;
serial_flow_dbg();
/*
* We can't control the number of start_tx in one startup cycle,
* which is determined by the number of SyS_write calls.
*
* If handle_tx exhausted the xmit buffer, it needs to be returned
* when xmit buffer is empty to prevent start_tx from hanging without
* data.
*/
if (uart_circ_empty(&port->state->xmit) || uart_tx_stopped(port)) {
nvt_stop_tx(port);
serial_dbg("uart_circ_empty or uart_tx_stopped, so return\n");
return;
}
if (nvt_port->tx_dma_en && port->line != 0) {
while (nvt_read(port, UART_TX_DMA_CTRL_REG) & TX_DMA_EN_BIT) {
serial_dbg("wait TX_DMA_EN_BIT auto clear\n");
;
}
nvt_enable_tx_dma(port);
} else {
nvt_masked_write(port, UART_IER_REG, THR_EMPTY_INTEN_BIT, THR_EMPTY_INTEN_BIT);
while (!(nvt_read(port, UART_IER_REG) & THR_EMPTY_INTEN_BIT)) {
;
}
lsr = nvt_read(port, UART_LSR_REG);
nvt_port->lsr_saved_flags |= lsr & LSR_SAVE_FLAGS;
if (lsr & THR_EMPTY_BIT) {
nvt_handle_tx(port);
}
}
}
static unsigned int nvt_tx_empty(struct uart_port *port)
{
struct nvt_port *nvt_port = UART_TO_NVT(port);
unsigned int lsr_r;
unsigned long flags;
serial_flow_dbg();
spin_lock_irqsave(&port->lock, flags);
lsr_r = nvt_read(port, UART_LSR_REG);
nvt_port->lsr_saved_flags |= lsr_r & LSR_SAVE_FLAGS;
spin_unlock_irqrestore(&port->lock, flags);
return (lsr_r & BOTH_EMPTY_BIT) ? TIOCSER_TEMT : 0;
}
static void nvt_handle_rx(struct uart_port *port, unsigned int lsr_r)
{
struct nvt_port *nvt_port = UART_TO_NVT(port);
struct tty_port *tport = &port->state->port;
unsigned char ch;
char flag;
serial_flow_dbg();
do {
if (likely(lsr_r & DATA_READY_BIT))
ch = nvt_read(port, UART_RBR_REG);
else
ch = 0;
flag = TTY_NORMAL;
port->icount.rx++;
serial_dbg("LSR(0x%x) IER(0x%x) cnt_rx(%d), read ch(0x%x) from RBR\n", lsr_r, nvt_read(port, UART_IER_REG), port->icount.rx, ch);
lsr_r |= nvt_port->lsr_saved_flags;
nvt_port->lsr_saved_flags = 0;
lsr_r |= nvt_port->lsr_break_flag;
nvt_port->lsr_break_flag = 0;
if (lsr_r & BREAK_INT_BIT) {
port->icount.brk++;
if (uart_handle_break(port))
continue;
} else if (lsr_r & PARITY_ERR_BIT)
port->icount.parity++;
else if (lsr_r & FRAMING_ERR_BIT)
port->icount.frame++;
if (lsr_r & OVERRUN_ERR_BIT)
port->icount.overrun++;
/*
* Mask off conditions which should be ignored.
*/
lsr_r &= port->read_status_mask;
if (lsr_r & BREAK_INT_BIT)
flag = TTY_BREAK;
else if (lsr_r & PARITY_ERR_BIT)
flag = TTY_PARITY;
else if (lsr_r & FRAMING_ERR_BIT)
flag = TTY_FRAME;
if (!(uart_handle_sysrq_char(port, ch)))
uart_insert_char(port, lsr_r, OVERRUN_ERR_BIT, ch, flag);
} while ((lsr_r = nvt_read(port, UART_LSR_REG)) & DATA_READY_BIT);
/*
* Drop the lock here since it might end up calling
* uart_start(), which takes the lock.
*/
spin_unlock(&port->lock);
tty_flip_buffer_push(tport);
spin_lock(&port->lock);
}
static void nvt_handle_tx(struct uart_port *port)
{
struct nvt_port *nvt_port = UART_TO_NVT(port);
struct circ_buf *xmit = &port->state->xmit;
unsigned char ch;
int count;
serial_flow_dbg();
/* For software flow control, xon resume transmission, xoff pause transmission */
if (port->x_char) {
nvt_write(port, UART_THR_REG, port->x_char);
port->icount.tx++;
port->x_char = 0;
return;
}
/* If there isn't anything more to transmit, or the uart is now stopped, disable the uart and exit */
if (uart_circ_empty(xmit) || uart_tx_stopped(port)) {
nvt_stop_tx(port);
return;
}
/* Drain the buffer by size of tx_loadsz in one cycle */
count = nvt_port->tx_loadsz;
do {
ch = xmit->buf[xmit->tail];
nvt_write(port, UART_THR_REG, ch);
port->icount.tx++;
serial_dbg("head(%d) tail(%d) cnt_tx(%d), write ch(0x%x) to THR\n", xmit->head, xmit->tail, port->icount.tx, ch);
xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
if (uart_circ_empty(xmit))
break;
} while (--count > 0);
/*
* If num chars in xmit buffer are too few, ask tty layer for more.
* By Hard ISR to schedule processing in software interrupt part.
*/
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) {
spin_unlock(&port->lock);
uart_write_wakeup(port);
spin_lock(&port->lock);
}
if (uart_circ_empty(xmit)) {
nvt_stop_tx(port);
}
}
static irqreturn_t nvt_irq(int irq, void *dev_id)
{
struct uart_port *port = dev_id;
struct nvt_port *nvt_port = UART_TO_NVT(port);
unsigned int lsr_r, ier_r, iir_r, dma_sts, dma_int;
unsigned long flags;
spin_lock_irqsave(&port->lock, flags);
lsr_r = nvt_read(port, UART_LSR_REG); /* error bits are cleared after reading */
ier_r = nvt_read(port, UART_IER_REG);
iir_r = nvt_read(port, UART_IIR_REG); /* clear interrupt status to avoid irq storm */
if ((nvt_port->tx_dma_en || nvt_port->rx_dma_en) && port->line != 0) {
dma_sts = nvt_read(port, UART_DMA_INT_STS_REG);
dma_int = nvt_read(port, UART_DMA_INT_CTRL_REG);
nvt_write(port, UART_DMA_INT_STS_REG, dma_sts); /* clear dma interrupt status */
serial_dbg("irq stage LSR(0x%x) IER(0x%x) IIR(0x%x) DMA_STS(0x%x) DMA_INT(0x%x)\n", lsr_r, ier_r, iir_r, dma_sts, dma_int);
} else {
serial_dbg("irq stage LSR(0x%x) IER(0x%x) IIR(0x%x)\n", lsr_r, ier_r, iir_r);
}
/* Handle UART rx */
if ((lsr_r & DATA_READY_BIT) && (ier_r & RDA_INTEN_BIT)) {
nvt_handle_rx(port, lsr_r);
} else if (nvt_port->rx_dma_en && port->line != 0) {
if ((dma_sts & RX_DMA_DONE_BIT) && (dma_int & RX_DMA_DONE_INTEN_BIT)) {
nvt_masked_write(port, UART_DMA_INT_CTRL_REG, RX_DMA_DONE_INTEN_BIT, 0);
nvt_masked_write(port, UART_DMA_INT_CTRL_REG, RX_DMA_ERR_INTEN_BIT, 0);
while (nvt_read(port, UART_RX_DMA_CTRL_REG) & RX_DMA_EN_BIT) {
serial_dbg("wait RX_DMA_EN_BIT auto clear\n");
;
}
nvt_handle_rx_dma(port, lsr_r);
schedule_work(&nvt_port->rx_dma_work);
} else if ((dma_sts & RX_DMA_ERR_BIT) && (dma_int & RX_DMA_ERR_INTEN_BIT)) {
nvt_masked_write(port, UART_DMA_INT_CTRL_REG, RX_DMA_DONE_INTEN_BIT, 0);
nvt_masked_write(port, UART_DMA_INT_CTRL_REG, RX_DMA_ERR_INTEN_BIT, 0);
nvt_masked_write(port, UART_RX_DMA_CTRL_REG, RX_DMA_EN_BIT, 0);
while (nvt_read(port, UART_RX_DMA_CTRL_REG) & RX_DMA_EN_BIT) {
;
}
nvt_handle_rx_dma(port, lsr_r);
if (dma_sts & DMA_OVERRUN_ERR_BIT)
serial_err("dma overrun error!\r\n");
if (dma_sts & DMA_PARITY_ERR_BIT)
serial_err("dma parity error!\r\n");
if (dma_sts & DMA_FRAMING_ERR_BIT)
serial_dbg("dma framing error!\r\n");
if (dma_sts & DMA_BREAK_ERR_BIT)
serial_dbg("dma break interrupt!\r\n");
schedule_work(&nvt_port->rx_dma_work);
}
}
/* Handle UART tx */
if ((lsr_r & THR_EMPTY_BIT) && (ier_r & THR_EMPTY_INTEN_BIT)) {
nvt_handle_tx(port);
} else if (nvt_port->tx_dma_en && port->line != 0) {
if ((dma_sts & TX_DMA_DONE_BIT) && (dma_int & TX_DMA_DONE_INTEN_BIT)) {
nvt_masked_write(port, UART_DMA_INT_CTRL_REG, TX_DMA_DONE_INTEN_BIT, 0);
}
}
/* Print UART error */
if (lsr_r & FIFO_DATA_ERR_BIT) {
if (lsr_r & OVERRUN_ERR_BIT)
serial_err("overrun error!\r\n");
if (lsr_r & PARITY_ERR_BIT)
serial_err("parity error!\r\n");
if (lsr_r & FRAMING_ERR_BIT)
serial_dbg("framing error!\r\n");
if (lsr_r & BREAK_INT_BIT)
serial_dbg("break interrupt!\r\n");
}
/* Clear RBR when IIR stuck in CRT status */
if (((iir_r & UART_IIR_INT_ID_MASK) == _UART_IIR_INT_CRT) && ((lsr_r & DATA_READY_BIT) == 0))
nvt_read(port, UART_RBR_REG);
spin_unlock_irqrestore(&port->lock, flags);
return IRQ_HANDLED;
}
static unsigned int nvt_get_mctrl(struct uart_port *port)
{
serial_flow_dbg();
/*
* Pretend we have a Modem status reg and following bits are
* always set, to satify the serial core state machine
* (DSR) Data Set Ready
* (CTS) Clear To Send
* (CAR) Carrier Detect
*/
return TIOCM_DSR | TIOCM_CTS | TIOCM_CAR;
}
static void nvt_set_mctrl(struct uart_port *port, unsigned int mctrl)
{
serial_flow_dbg();
/* MCR not present */
}
static void nvt_break_ctl(struct uart_port *port, int break_ctl)
{
unsigned long flags;
serial_flow_dbg();
spin_lock_irqsave(&port->lock, flags);
if (break_ctl)
nvt_masked_write(port, UART_LCR_REG, SET_BREAK_BIT, SET_BREAK_BIT);
else
nvt_masked_write(port, UART_LCR_REG, SET_BREAK_BIT, 0);
spin_unlock_irqrestore(&port->lock, flags);
}
static int nvt_startup(struct uart_port *port)
{
struct nvt_port *nvt_port = UART_TO_NVT(port);
int ret;
serial_flow_dbg();
snprintf(nvt_port->name, sizeof(nvt_port->name),
"nvt_serial%d", port->line);
ret = request_irq(port->irq, nvt_irq, IRQF_TRIGGER_HIGH,
nvt_port->name, port);
if (unlikely(ret))
return ret;
/*
* Clear the FIFO buffers and disable.
*/
nvt_clear_fifos(port);
/*
* Clear the interrupt registers again for luck, and clear the
* saved flags to avoid getting false values from polling
* routines or the previous session.
*/
nvt_read(port, UART_LSR_REG);
nvt_read(port, UART_THR_REG);
nvt_read(port, UART_IIR_REG);
nvt_port->lsr_saved_flags = 0;
nvt_port->lsr_break_flag = 0;
#if defined(NVT_FR) && defined(CONFIG_SERIAL_NVT_DMA)
/* Set DMA feature */
nvt_port->tx_dma_en = 1;
nvt_port->rx_dma_en = 1;
#endif
/* A debug node to enable/disable DMA feature */
if (__serial_tx_dma_en_debug == 1)
nvt_port->tx_dma_en = 1;
else if (__serial_tx_dma_en_debug == 0)
nvt_port->tx_dma_en = 0;
if (__serial_rx_dma_en_debug == 1)
nvt_port->rx_dma_en = 1;
else if (__serial_rx_dma_en_debug == 0)
nvt_port->rx_dma_en = 0;
/* Allocate DMA */
if (nvt_port->tx_dma_en && port->line != 0) {
nvt_port->tx_virt_addr = dma_alloc_coherent(port->dev, UART_XMIT_SIZE, &nvt_port->tx_dma_addr, GFP_KERNEL);
if (!nvt_port->tx_virt_addr) {
serial_err("allocate DMA failed, using PIO\n");
nvt_port->tx_dma_en = 0;
} else {
serial_dbg("allocate tx_virt_addr(0x%x) tx_dma_addr(0x%x)\n", (unsigned int)nvt_port->tx_virt_addr, nvt_port->tx_dma_addr);
}
}
if (nvt_port->rx_dma_en && port->line != 0) {
nvt_port->rx_virt_addr = dma_alloc_coherent(port->dev, UART_XMIT_SIZE, &nvt_port->rx_dma_addr, GFP_KERNEL);
if (!nvt_port->rx_virt_addr) {
serial_err("allocate DMA failed, using PIO\n");
nvt_port->rx_dma_en = 0;
} else if (!IS_ALIGNED(nvt_port->rx_dma_addr, (dma_addr_t)RX_DMA_ADDR_ALIGN) && (nvt_get_chip_id() == CHIP_NA51055 || nvt_get_chip_id() == CHIP_NA51084)) {
serial_err("rx_dma_addr(0x%x) failed to align %d bytes, using PIO\n", nvt_port->rx_dma_addr, RX_DMA_ADDR_ALIGN);
nvt_port->rx_dma_en = 0;
} else {
serial_dbg("allocate rx_virt_addr(0x%x) rx_dma_addr(0x%x)\n", (unsigned int)nvt_port->rx_virt_addr, nvt_port->rx_dma_addr);
}
}
return 0;
}
static void nvt_shutdown(struct uart_port *port)
{
struct nvt_port *nvt_port = UART_TO_NVT(port);
unsigned long flags;
serial_flow_dbg();
spin_lock_irqsave(&port->lock, flags);
nvt_write(port, UART_IER_REG, 0); /* disable interrupts to avoid hanging in start_tx */
spin_unlock_irqrestore(&port->lock, flags);
free_irq(port->irq, port);
/* Free DMA */
if ((nvt_port->tx_dma_en || nvt_port->rx_dma_en) && port->line != 0) {
if (nvt_port->tx_virt_addr) {
serial_dbg("free tx_virt_addr(0x%x) tx_dma_addr(0x%x)\n", (unsigned int)nvt_port->tx_virt_addr, nvt_port->tx_dma_addr);
dma_free_coherent(port->dev, UART_XMIT_SIZE, nvt_port->tx_virt_addr, nvt_port->tx_dma_addr);
nvt_port->tx_virt_addr = NULL;
}
if (nvt_port->rx_virt_addr) {
serial_dbg("free rx_virt_addr(0x%x) rx_dma_addr(0x%x)\n", (unsigned int)nvt_port->rx_virt_addr, nvt_port->rx_dma_addr);
dma_free_coherent(port->dev, UART_XMIT_SIZE, nvt_port->rx_virt_addr, nvt_port->rx_dma_addr);
nvt_port->rx_virt_addr = NULL;
}
nvt_write(port, UART_DMA_INT_CTRL_REG, 0);
nvt_write(port, UART_TX_DMA_CTRL_REG, 0);
nvt_write(port, UART_RX_DMA_CTRL_REG, 0);
}
}
static void nvt_set_baud_rate(struct uart_port *port, unsigned int baud)
{
unsigned int PSR, DLR;
PSR = 0x01;
DLR = port->uartclk / 16 / PSR / baud;
if (DLR > UART_DLR_MAX) {
DLR = UART_DLR_MAX;
serial_err("baud out of MAX range error!\n");
}
if (DLR < UART_DLR_MIN) {
DLR = UART_DLR_MIN;
serial_err("baud out of MIN range error!\n");
}
nvt_write(port, UART_DLL_REG, DLR & DLL_MASK);
nvt_masked_write(port, UART_DLM_REG, DLM_MASK, DLR >> 8);
nvt_write(port, UART_PSR_REG, PSR & PSR_MASK);
}
static void nvt_set_termios(struct uart_port *port, struct ktermios *termios,
struct ktermios *old)
{
struct nvt_port *nvt_port = UART_TO_NVT(port);
unsigned long flags;
unsigned int lcr_val = 0, baud;
serial_flow_dbg();
spin_lock_irqsave(&port->lock, flags);
/* Set baud rate and divisor */
if (nvt_port->baud) {
if (tty_termios_baud_rate(termios))
tty_termios_encode_baud_rate(termios, nvt_port->baud, nvt_port->baud);
nvt_port->baud = 0;
}
baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk / 16);
nvt_masked_write(port, UART_LCR_REG, DLAB_BIT, DLAB_BIT); /* set DLAB bit to access divisor */
nvt_set_baud_rate(port, baud);
if (tty_termios_baud_rate(termios))
tty_termios_encode_baud_rate(termios, baud, baud);
/* Set length */
switch (termios->c_cflag & CSIZE) {
case CS5:
lcr_val |= _WL_L5;
break;
case CS6:
lcr_val |= _WL_L6;
break;
case CS7:
lcr_val |= _WL_L7;
break;
case CS8:
default:
lcr_val |= _WL_L8;
break;
}
/* Set stop bits */
if (termios->c_cflag & CSTOPB)
lcr_val |= STOP_BIT;
/* Set parity */
if (termios->c_cflag & PARENB) {
if (termios->c_cflag & CMSPAR) {
if (termios->c_cflag & PARODD)
lcr_val |= _UART_PARITY_ONE << _UART_LCR_PARITY_SHIFT;
else
lcr_val |= _UART_PARITY_ZERO << _UART_LCR_PARITY_SHIFT;
} else {
if (termios->c_cflag & PARODD)
lcr_val |= _UART_PARITY_ODD << _UART_LCR_PARITY_SHIFT;
else
lcr_val |= _UART_PARITY_EVEN << _UART_LCR_PARITY_SHIFT;
}
}
nvt_write(port, UART_LCR_REG, lcr_val); /* clear DLAB bit */
/* Set rx fifo trigger level */
nvt_clear_fifos(port);
nvt_write(port, UART_FCR_REG, FIFO_EN_BIT | (nvt_port->rx_trig_level << _UART_FCR_RX_TRIGGER_SHIFT));
/* Set hardware flow control */
if (nvt_port->hw_flowctrl) {
termios->c_cflag |= CRTSCTS;
nvt_port->hw_flowctrl = 0;
}
#ifdef NVT_FR
if (termios->c_cflag & CRTSCTS) {
nvt_masked_write(port, UART_MCR_REG, HW_FLOW_CTRL_BIT, HW_FLOW_CTRL_BIT);
} else {
nvt_masked_write(port, UART_MCR_REG, HW_FLOW_CTRL_BIT, 0);
}
#else
if (termios->c_cflag & CRTSCTS) {
nvt_masked_write(port, UART_IER_REG, HW_FLOW_CTRL_BIT, HW_FLOW_CTRL_BIT);
} else {
nvt_masked_write(port, UART_IER_REG, HW_FLOW_CTRL_BIT, 0);
}
#endif
#ifdef NVT_FR
/* Set rs485 feature */
if (nvt_port->rs485_en) {
nvt_write(port, UART_RS485_REG, ENABLE_BIT
| (nvt_port->rs485_delay_before_send << _UART_RS485_SETUP_TIME_SHIFT)
| (nvt_port->rs485_delay_after_send << _UART_RS485_HOLD_TIME_SHIFT));
}
#endif
if (nvt_port->rx_dma_en && port->line != 0) {
schedule_work(&nvt_port->rx_dma_work);
} else {
/* Enanle receive interrupts */
nvt_write(port, UART_IER_REG, RLS_INTEN_BIT | RDA_INTEN_BIT);
}
/* Configure status bits to ignore based on termio flags */
port->read_status_mask = OVERRUN_ERR_BIT;
if (termios->c_iflag & INPCK)
port->read_status_mask |= FRAMING_ERR_BIT | PARITY_ERR_BIT;
if (termios->c_iflag & (IGNBRK | BRKINT | PARMRK))
port->read_status_mask |= BREAK_INT_BIT;
uart_update_timeout(port, termios->c_cflag, baud);
serial_dbg("baud(%d) hw_flowctrl(%d) tx_dma(%d) rx_dma(%d)\n", baud, ((termios->c_cflag & CRTSCTS) ? 1 : 0), nvt_port->tx_dma_en, nvt_port->rx_dma_en);
spin_unlock_irqrestore(&port->lock, flags);
}
static const char *nvt_type(struct uart_port *port)
{
return "NVT";
}
static void nvt_release_port(struct uart_port *port)
{
serial_flow_dbg();
}
static int nvt_request_port(struct uart_port *port)
{
serial_flow_dbg();
return 0;
}
static void nvt_config_port(struct uart_port *port, int flags)
{
if (flags & UART_CONFIG_TYPE)
port->type = PORT_16550A;
}
static int nvt_verify_port(struct uart_port *port, struct serial_struct *ser)
{
serial_flow_dbg();
if (unlikely(ser->type != PORT_UNKNOWN && ser->type != PORT_16550A))
return -EINVAL;
if (unlikely(port->irq != ser->irq))
return -EINVAL;
return 0;
}
static inline void wait_for_xmitr(struct uart_port *port, int bits)
{
struct nvt_port *nvt_port = UART_TO_NVT(port);
unsigned int status, tmout = 10000;
/* Wait up to 10ms for the character(s) to be sent */
for (;;) {
status = nvt_read(port, UART_LSR_REG);
nvt_port->lsr_saved_flags |= status & LSR_SAVE_FLAGS;
if (status & BREAK_INT_BIT)
nvt_port->lsr_break_flag = BREAK_INT_BIT;
if ((status & bits) == bits)
break;
if (--tmout == 0)
break;
udelay(1);
}
}
#ifdef CONFIG_CONSOLE_POLL
static int nvt_poll_get_char(struct uart_port *port)
{
unsigned char ch;
while (!(nvt_read(port, UART_LSR_REG) & DATA_READY_BIT))
cpu_relax();
ch = nvt_read(port, UART_RBR_REG);
return ch;
}
static void nvt_poll_put_char(struct uart_port *port, unsigned char ch)
{
unsigned int ier;
/* First save the IER then disable the interrupts */
ier = nvt_read(port, UART_IER_REG);
nvt_write(port, UART_IER_REG, 0);
wait_for_xmitr(port, BOTH_EMPTY_BIT);
/* Send the character out */
nvt_write(port, UART_THR_REG, ch);
/* Finally, wait for transmitter to become empty and restore the IER */
wait_for_xmitr(port, BOTH_EMPTY_BIT);
nvt_write(port, UART_IER_REG, ier);
}
#endif
static struct uart_ops nvt_uart_pops = {
.stop_rx = nvt_stop_rx,
.stop_tx = nvt_stop_tx,
.start_tx = nvt_start_tx,
.tx_empty = nvt_tx_empty,
.get_mctrl = nvt_get_mctrl,
.set_mctrl = nvt_set_mctrl,
.break_ctl = nvt_break_ctl,
.startup = nvt_startup,
.shutdown = nvt_shutdown,
.set_termios = nvt_set_termios,
.type = nvt_type,
.release_port = nvt_release_port,
.request_port = nvt_request_port,
.config_port = nvt_config_port,
.verify_port = nvt_verify_port,
#ifdef CONFIG_CONSOLE_POLL
.poll_get_char = nvt_poll_get_char,
.poll_put_char = nvt_poll_put_char,
#endif
};
static struct nvt_port nvt_uart_ports[] = {
#ifdef CONFIG_SERIAL_NVT_CONSOLE
{
.uart = {
.line = 0,
.ops = &nvt_uart_pops,
.flags = UPF_BOOT_AUTOCONF,
/**********************************************************************
* Following items are default values and will be overwritten by dts *
**********************************************************************/
.uartclk = 24000000,
.iotype = UPIO_MEM32,
.fifosize = 64,
.regshift = 2,
},
.baud = 115200, /* set into termios structure when first used, and can be modified through user interface such as stty */
.tx_loadsz = 64, /* maximum number of characters tx can send in one handle_tx cycle, usually following fifosize */
.hw_flowctrl = 0, /* default hardware flow control is on or off, also we can use stty crtscts/-crtscts to turn on/off */
.rx_trig_level = 3,
},
#endif
{
.uart = {
.line = 1,
.ops = &nvt_uart_pops,
.flags = UPF_BOOT_AUTOCONF,
/**********************************************************************
* Following items are default values and will be overwritten by dts *
**********************************************************************/
.uartclk = 48000000,
.iotype = UPIO_MEM32,
.fifosize = 64,
.regshift = 2,
},
.baud = 115200,
.tx_loadsz = 64,
.hw_flowctrl = 0,
.rx_trig_level = 3,
},
{
.uart = {
.line = 2,
.ops = &nvt_uart_pops,
.flags = UPF_BOOT_AUTOCONF,
/**********************************************************************
* Following items are default values and will be overwritten by dts *
**********************************************************************/
.uartclk = 48000000,
.iotype = UPIO_MEM32,
.fifosize = 64,
.regshift = 2,
},
.baud = 0,
.tx_loadsz = 64,
.hw_flowctrl = 0,
.rx_trig_level = 3,
},
{
.uart = {
.line = 3,
.ops = &nvt_uart_pops,
.flags = UPF_BOOT_AUTOCONF,
/**********************************************************************
* Following items are default values and will be overwritten by dts *
**********************************************************************/
.uartclk = 48000000,
.iotype = UPIO_MEM32,
.fifosize = 64,
.regshift = 2,
},
.baud = 0,
.tx_loadsz = 64,
.hw_flowctrl = 0,
.rx_trig_level = 3,
},
{
.uart = {
.line = 4,
.ops = &nvt_uart_pops,
.flags = UPF_BOOT_AUTOCONF,
/**********************************************************************
* Following items are default values and will be overwritten by dts *
**********************************************************************/
.uartclk = 48000000,
.iotype = UPIO_MEM32,
.fifosize = 64,
.regshift = 2,
},
.baud = 0,
.tx_loadsz = 64,
.hw_flowctrl = 0,
.rx_trig_level = 3,
},
{
.uart = {
.line = 5,
.ops = &nvt_uart_pops,
.flags = UPF_BOOT_AUTOCONF,
/**********************************************************************
* Following items are default values and will be overwritten by dts *
**********************************************************************/
.uartclk = 48000000,
.iotype = UPIO_MEM32,
.fifosize = 64,
.regshift = 2,
},
.baud = 0,
.tx_loadsz = 64,
.hw_flowctrl = 0,
.rx_trig_level = 3,
},
};
static int UART_NR;
static inline struct uart_port *get_port_from_line(unsigned int line)
{
#ifdef CONFIG_SERIAL_NVT_CONSOLE
return &nvt_uart_ports[line].uart;
#else
return &nvt_uart_ports[line-1].uart;
#endif
}
#ifdef CONFIG_SERIAL_NVT_CONSOLE
static void nvt_console_putchar(struct uart_port *port, int ch)
{
wait_for_xmitr(port, THR_EMPTY_BIT);
nvt_write(port, UART_THR_REG, ch);
}
static void nvt_console_write(struct console *co, const char *s, unsigned int count)
{
struct uart_port *port = get_port_from_line(co->index);
unsigned long flags;
unsigned int ier;
int locked = 1;
if (port->sysrq)
locked = 0;
else if (oops_in_progress)
locked = spin_trylock_irqsave(&port->lock, flags);
else
spin_lock_irqsave(&port->lock, flags);
/* First save the IER then disable the interrupts */
ier = nvt_read(port, UART_IER_REG);
nvt_write(port, UART_IER_REG, 0);
uart_console_write(port, s, count, nvt_console_putchar);
/* Finally, wait for transmitter to become empty and restore the IER */
wait_for_xmitr(port, BOTH_EMPTY_BIT);
nvt_write(port, UART_IER_REG, ier);
if (locked)
spin_unlock_irqrestore(&port->lock, flags);
}
static int __init nvt_console_setup(struct console *co, char *options)
{
struct uart_port *port;
int baud = 115200;
int bits = 8;
int parity = 'n';
int flow = 'n';
if (unlikely(co->index >= UART_NR || co->index < 0))
return -ENXIO;
/*
* The uart port backing the console (e.g. ttyS0) might not have been
* init yet. If so, defer the console setup to after the port.
*/
port = get_port_from_line(co->index);
if (unlikely(!port->membase))
return -ENXIO;
if (options)
uart_parse_options(options, &baud, &parity, &bits, &flow);
pr_info("nvt_serial: console setup on port #%d\n", port->line);
/*
* Serial core will call port->ops->set_termios( )
* which will set the baud reg.
*/
return uart_set_options(port, co, baud, parity, bits, flow);
}
static void nvt_serial_early_putchar(struct uart_port *port, int ch)
{
wait_for_xmitr(port, THR_EMPTY_BIT);
nvt_write_early(port, UART_THR_REG, ch);
}
static void nvt_serial_early_write(struct console *con, const char *s, unsigned n)
{
struct earlycon_device *dev = con->data;
uart_console_write(&dev->port, s, n, nvt_serial_early_putchar);
}
static int __init nvt_serial_early_console_setup(struct earlycon_device *device, const char *opt)
{
if (!device->port.membase)
return -ENODEV;
device->con->write = nvt_serial_early_write;
return 0;
}
EARLYCON_DECLARE(nvt_serial, nvt_serial_early_console_setup);
OF_EARLYCON_DECLARE(nvt_serial, "nvt.nvt_uart", nvt_serial_early_console_setup);
static struct uart_driver nvt_uart_driver;
static struct console nvt_console = {
.name = "ttyS",
.write = nvt_console_write,
.device = uart_console_device,
.setup = nvt_console_setup,
.flags = CON_PRINTBUFFER,
.index = -1,
.data = &nvt_uart_driver,
};
#define NVT_CONSOLE (&nvt_console)
#else
#define NVT_CONSOLE NULL
#endif
static struct uart_driver nvt_uart_driver = {
.owner = THIS_MODULE,
.driver_name = DRIVER_NAME,
.dev_name = "ttyS",
.cons = NVT_CONSOLE,
};
static int nvt_serial_probe(struct platform_device *pdev)
{
struct nvt_port *nvt_port;
struct uart_port *port;
u32 prop, prop_array[2] = {0};
int line, irq;
struct resource *resource;
if (of_property_read_u32(pdev->dev.of_node, "uart_id", &prop) == 0) {
line = prop;
} else {
dev_dbg(&pdev->dev, "get uart_id failed, use default uart_id(%d)\n", global_line);
line = global_line;
global_line++;
}
if (line >= UART_NR) {
dev_err(&pdev->dev, "unsupported uart line(%d)\n", line);
return -ENXIO;
}
port = get_port_from_line(line);
port->dev = &pdev->dev;
nvt_port = UART_TO_NVT(port);
spin_lock_init(&nvt_port->write_lock);
INIT_WORK(&nvt_port->rx_dma_work, nvt_enable_rx_dma);
resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (unlikely(!resource)) {
dev_err(&pdev->dev, "get address failed\n");
return -ENXIO;
}
port->mapbase = resource->start;
port->membase = devm_ioremap(port->dev, resource->start, resource_size(resource));
if (!port->membase) {
dev_err(&pdev->dev, "get ioremap failed\n");
return -EBUSY;
}
irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
if (unlikely(irq < 0)) {
dev_err(&pdev->dev, "get irq failed\n");
return -ENXIO;
}
port->irq = irq;
if (line == 0) {
if (nvt_get_chip_id() == CHIP_NA51055) {
port->fifosize = 16;
nvt_port->tx_loadsz = 16;
}
}
if (of_property_read_u32(pdev->dev.of_node, "reg-shift", &prop) == 0)
port->regshift = prop;
if (of_property_read_u32(pdev->dev.of_node, "reg-io-width", &prop) == 0) {
switch (prop) {
case 1:
port->iotype = UPIO_MEM;
break;
case 4:
port->iotype = of_device_is_big_endian(pdev->dev.of_node) ?
UPIO_MEM32BE : UPIO_MEM32;
break;
default:
dev_warn(&pdev->dev, "unsupported reg-io-width(%d)\n", prop);
}
}
if (of_find_property(pdev->dev.of_node, "no-loopback-test", NULL))
port->flags |= UPF_SKIP_TEST;
if (of_property_read_u32(pdev->dev.of_node, "clock-frequency", &prop) == 0) {
port->uartclk = prop;
} else {
dev_dbg(&pdev->dev, "get clock-frequency failed, use default uartclk(%d)\n", port->uartclk);
}
nvt_port->clk = clk_get(&pdev->dev, dev_name(&pdev->dev));
if (IS_ERR(nvt_port->clk)) {
dev_err(&pdev->dev, "clk %s not found\n", dev_name(&pdev->dev));
return PTR_ERR(nvt_port->clk);
} else {
if (!__clk_is_enabled(nvt_port->clk))
clk_prepare_enable(nvt_port->clk);
clk_set_rate(nvt_port->clk, port->uartclk);
}
if (of_property_read_u32(pdev->dev.of_node, "baud", &prop) == 0)
nvt_port->baud = prop;
if (of_property_read_u32(pdev->dev.of_node, "rx_trig_level", &prop) == 0)
nvt_port->rx_trig_level = prop;
if (of_property_read_u32(pdev->dev.of_node, "hw_flowctrl", &prop) == 0)
nvt_port->hw_flowctrl = prop;
if (of_property_read_u32(pdev->dev.of_node, "rs485_en", &prop) == 0) {
nvt_port->rs485_en = prop;
if (of_property_read_u32_array(pdev->dev.of_node, "rs485_delay", prop_array, 2) == 0) {
nvt_port->rs485_delay_before_send = prop_array[0];
nvt_port->rs485_delay_after_send = prop_array[1];
}
}
serial_dbg("driver probed\n");
platform_set_drvdata(pdev, port);
return uart_add_one_port(&nvt_uart_driver, port);
}
static int nvt_serial_remove(struct platform_device *pdev)
{
struct uart_port *port = platform_get_drvdata(pdev);
uart_remove_one_port(&nvt_uart_driver, port);
return 0;
}
static const struct of_device_id nvt_match_table[] = {
{ .compatible = "nvt.nvt_uart" },
{ .compatible = "ns16550a" },
{},
};
static struct platform_driver nvt_serial_platform_driver = {
.probe = nvt_serial_probe,
.remove = nvt_serial_remove,
.driver = {
.name = "nvt_serial",
.of_match_table = nvt_match_table,
},
};
static int __init nvt_serial_init(void)
{
int ret;
if (nvt_get_chip_id() == CHIP_NA51084) {
UART_NR = 6;
} else {
UART_NR = 3;
}
nvt_uart_driver.nr = UART_NR;
ret = uart_register_driver(&nvt_uart_driver);
if (unlikely(ret))
return ret;
ret = platform_driver_register(&nvt_serial_platform_driver);
if (unlikely(ret)) {
uart_unregister_driver(&nvt_uart_driver);
return ret;
}
pr_info("nvt_serial: driver initialized\n");
return ret;
}
static void __exit nvt_serial_exit(void)
{
platform_driver_unregister(&nvt_serial_platform_driver);
uart_unregister_driver(&nvt_uart_driver);
}
module_init(nvt_serial_init);
module_exit(nvt_serial_exit);
MODULE_AUTHOR("Shawn Chou <shawn_chou@novatek.com.tw>");
MODULE_DESCRIPTION("Driver for NVT Soc");
MODULE_VERSION(DRIVER_VERSION);
MODULE_LICENSE("GPL");