serial: Add driver for the Altera UART

Add an UART driver for the UART component available as a SOPC (System on
Programmable Chip) component for Altera FPGAs.

Signed-off-by: Tobias Klauser <tklauser@distanz.ch>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
This commit is contained in:
Tobias Klauser 2010-05-05 10:35:23 +02:00 committed by Greg Kroah-Hartman
parent 5bcd601049
commit 6b7d8f8b5c
5 changed files with 617 additions and 0 deletions

View File

@ -1544,4 +1544,35 @@ config SERIAL_ALTERA_JTAGUART_CONSOLE_BYPASS
Bypass console output and keep going even if there is no
JTAG terminal connection with the host.
config SERIAL_ALTERA_UART
tristate "Altera UART support"
select SERIAL_CORE
help
This driver supports the Altera softcore UART port.
config SERIAL_ALTERA_UART_MAXPORTS
int "Maximum number of Altera UART ports"
depends on SERIAL_ALTERA_UART
default 4
help
This setting lets you define the maximum number of the Altera
UART ports. The usual default varies from board to board, and
this setting is a way of catering for that.
config SERIAL_ALTERA_UART_BAUDRATE
int "Default baudrate for Altera UART ports"
depends on SERIAL_ALTERA_UART
default 115200
help
This setting lets you define what the default baudrate is for the
Altera UART ports. The usual default varies from board to board,
and this setting is a way of catering for that.
config SERIAL_ALTERA_UART_CONSOLE
bool "Altera UART console support"
depends on SERIAL_ALTERA_UART=y
select SERIAL_CORE_CONSOLE
help
Enable a Altera UART port to be the system console.
endmenu

View File

@ -83,3 +83,4 @@ obj-$(CONFIG_SERIAL_QE) += ucc_uart.o
obj-$(CONFIG_SERIAL_TIMBERDALE) += timbuart.o
obj-$(CONFIG_SERIAL_GRLIB_GAISLER_APBUART) += apbuart.o
obj-$(CONFIG_SERIAL_ALTERA_JTAGUART) += altera_jtaguart.o
obj-$(CONFIG_SERIAL_ALTERA_UART) += altera_uart.o

View File

@ -0,0 +1,570 @@
/*
* altera_uart.c -- Altera UART driver
*
* Based on mcf.c -- Freescale ColdFire UART driver
*
* (C) Copyright 2003-2007, Greg Ungerer <gerg@snapgear.com>
* (C) Copyright 2008, Thomas Chou <thomas@wytron.com.tw>
* (C) Copyright 2010, Tobias Klauser <tklauser@distanz.ch>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/console.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial.h>
#include <linux/serial_core.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/altera_uart.h>
#define DRV_NAME "altera_uart"
/*
* Altera UART register definitions according to the Nios UART datasheet:
* http://www.altera.com/literature/ds/ds_nios_uart.pdf
*/
#define ALTERA_UART_SIZE 32
#define ALTERA_UART_RXDATA_REG 0
#define ALTERA_UART_TXDATA_REG 4
#define ALTERA_UART_STATUS_REG 8
#define ALTERA_UART_CONTROL_REG 12
#define ALTERA_UART_DIVISOR_REG 16
#define ALTERA_UART_EOP_REG 20
#define ALTERA_UART_STATUS_PE_MSK 0x0001 /* parity error */
#define ALTERA_UART_STATUS_FE_MSK 0x0002 /* framing error */
#define ALTERA_UART_STATUS_BRK_MSK 0x0004 /* break */
#define ALTERA_UART_STATUS_ROE_MSK 0x0008 /* RX overrun error */
#define ALTERA_UART_STATUS_TOE_MSK 0x0010 /* TX overrun error */
#define ALTERA_UART_STATUS_TMT_MSK 0x0020 /* TX shift register state */
#define ALTERA_UART_STATUS_TRDY_MSK 0x0040 /* TX ready */
#define ALTERA_UART_STATUS_RRDY_MSK 0x0080 /* RX ready */
#define ALTERA_UART_STATUS_E_MSK 0x0100 /* exception condition */
#define ALTERA_UART_STATUS_DCTS_MSK 0x0400 /* CTS logic-level change */
#define ALTERA_UART_STATUS_CTS_MSK 0x0800 /* CTS logic state */
#define ALTERA_UART_STATUS_EOP_MSK 0x1000 /* EOP written/read */
/* Enable interrupt on... */
#define ALTERA_UART_CONTROL_PE_MSK 0x0001 /* ...parity error */
#define ALTERA_UART_CONTROL_FE_MSK 0x0002 /* ...framing error */
#define ALTERA_UART_CONTROL_BRK_MSK 0x0004 /* ...break */
#define ALTERA_UART_CONTROL_ROE_MSK 0x0008 /* ...RX overrun */
#define ALTERA_UART_CONTROL_TOE_MSK 0x0010 /* ...TX overrun */
#define ALTERA_UART_CONTROL_TMT_MSK 0x0020 /* ...TX shift register empty */
#define ALTERA_UART_CONTROL_TRDY_MSK 0x0040 /* ...TX ready */
#define ALTERA_UART_CONTROL_RRDY_MSK 0x0080 /* ...RX ready */
#define ALTERA_UART_CONTROL_E_MSK 0x0100 /* ...exception*/
#define ALTERA_UART_CONTROL_TRBK_MSK 0x0200 /* TX break */
#define ALTERA_UART_CONTROL_DCTS_MSK 0x0400 /* Interrupt on CTS change */
#define ALTERA_UART_CONTROL_RTS_MSK 0x0800 /* RTS signal */
#define ALTERA_UART_CONTROL_EOP_MSK 0x1000 /* Interrupt on EOP */
/*
* Local per-uart structure.
*/
struct altera_uart {
struct uart_port port;
unsigned int sigs; /* Local copy of line sigs */
unsigned short imr; /* Local IMR mirror */
};
static unsigned int altera_uart_tx_empty(struct uart_port *port)
{
return (readl(port->membase + ALTERA_UART_STATUS_REG) &
ALTERA_UART_STATUS_TMT_MSK) ? TIOCSER_TEMT : 0;
}
static unsigned int altera_uart_get_mctrl(struct uart_port *port)
{
struct altera_uart *pp = container_of(port, struct altera_uart, port);
unsigned long flags;
unsigned int sigs;
spin_lock_irqsave(&port->lock, flags);
sigs =
(readl(port->membase + ALTERA_UART_STATUS_REG) &
ALTERA_UART_STATUS_CTS_MSK) ? TIOCM_CTS : 0;
sigs |= (pp->sigs & TIOCM_RTS);
spin_unlock_irqrestore(&port->lock, flags);
return sigs;
}
static void altera_uart_set_mctrl(struct uart_port *port, unsigned int sigs)
{
struct altera_uart *pp = container_of(port, struct altera_uart, port);
unsigned long flags;
spin_lock_irqsave(&port->lock, flags);
pp->sigs = sigs;
if (sigs & TIOCM_RTS)
pp->imr |= ALTERA_UART_CONTROL_RTS_MSK;
else
pp->imr &= ~ALTERA_UART_CONTROL_RTS_MSK;
writel(pp->imr, port->membase + ALTERA_UART_CONTROL_REG);
spin_unlock_irqrestore(&port->lock, flags);
}
static void altera_uart_start_tx(struct uart_port *port)
{
struct altera_uart *pp = container_of(port, struct altera_uart, port);
unsigned long flags;
spin_lock_irqsave(&port->lock, flags);
pp->imr |= ALTERA_UART_CONTROL_TRDY_MSK;
writel(pp->imr, port->membase + ALTERA_UART_CONTROL_REG);
spin_unlock_irqrestore(&port->lock, flags);
}
static void altera_uart_stop_tx(struct uart_port *port)
{
struct altera_uart *pp = container_of(port, struct altera_uart, port);
unsigned long flags;
spin_lock_irqsave(&port->lock, flags);
pp->imr &= ~ALTERA_UART_CONTROL_TRDY_MSK;
writel(pp->imr, port->membase + ALTERA_UART_CONTROL_REG);
spin_unlock_irqrestore(&port->lock, flags);
}
static void altera_uart_stop_rx(struct uart_port *port)
{
struct altera_uart *pp = container_of(port, struct altera_uart, port);
unsigned long flags;
spin_lock_irqsave(&port->lock, flags);
pp->imr &= ~ALTERA_UART_CONTROL_RRDY_MSK;
writel(pp->imr, port->membase + ALTERA_UART_CONTROL_REG);
spin_unlock_irqrestore(&port->lock, flags);
}
static void altera_uart_break_ctl(struct uart_port *port, int break_state)
{
struct altera_uart *pp = container_of(port, struct altera_uart, port);
unsigned long flags;
spin_lock_irqsave(&port->lock, flags);
if (break_state == -1)
pp->imr |= ALTERA_UART_CONTROL_TRBK_MSK;
else
pp->imr &= ~ALTERA_UART_CONTROL_TRBK_MSK;
writel(pp->imr, port->membase + ALTERA_UART_CONTROL_REG);
spin_unlock_irqrestore(&port->lock, flags);
}
static void altera_uart_enable_ms(struct uart_port *port)
{
}
static void altera_uart_set_termios(struct uart_port *port,
struct ktermios *termios,
struct ktermios *old)
{
unsigned long flags;
unsigned int baud, baudclk;
baud = uart_get_baud_rate(port, termios, old, 0, 4000000);
baudclk = port->uartclk / baud;
if (old)
tty_termios_copy_hw(termios, old);
tty_termios_encode_baud_rate(termios, baud, baud);
spin_lock_irqsave(&port->lock, flags);
writel(baudclk, port->membase + ALTERA_UART_DIVISOR_REG);
spin_unlock_irqrestore(&port->lock, flags);
}
static void altera_uart_rx_chars(struct altera_uart *pp)
{
struct uart_port *port = &pp->port;
unsigned char ch, flag;
unsigned short status;
while ((status = readl(port->membase + ALTERA_UART_STATUS_REG)) &
ALTERA_UART_STATUS_RRDY_MSK) {
ch = readl(port->membase + ALTERA_UART_RXDATA_REG);
flag = TTY_NORMAL;
port->icount.rx++;
if (status & ALTERA_UART_STATUS_E_MSK) {
writel(status, port->membase + ALTERA_UART_STATUS_REG);
if (status & ALTERA_UART_STATUS_BRK_MSK) {
port->icount.brk++;
if (uart_handle_break(port))
continue;
} else if (status & ALTERA_UART_STATUS_PE_MSK) {
port->icount.parity++;
} else if (status & ALTERA_UART_STATUS_ROE_MSK) {
port->icount.overrun++;
} else if (status & ALTERA_UART_STATUS_FE_MSK) {
port->icount.frame++;
}
status &= port->read_status_mask;
if (status & ALTERA_UART_STATUS_BRK_MSK)
flag = TTY_BREAK;
else if (status & ALTERA_UART_STATUS_PE_MSK)
flag = TTY_PARITY;
else if (status & ALTERA_UART_STATUS_FE_MSK)
flag = TTY_FRAME;
}
if (uart_handle_sysrq_char(port, ch))
continue;
uart_insert_char(port, status, ALTERA_UART_STATUS_ROE_MSK, ch,
flag);
}
tty_flip_buffer_push(port->state->port.tty);
}
static void altera_uart_tx_chars(struct altera_uart *pp)
{
struct uart_port *port = &pp->port;
struct circ_buf *xmit = &port->state->xmit;
if (port->x_char) {
/* Send special char - probably flow control */
writel(port->x_char, port->membase + ALTERA_UART_TXDATA_REG);
port->x_char = 0;
port->icount.tx++;
return;
}
while (readl(port->membase + ALTERA_UART_STATUS_REG) &
ALTERA_UART_STATUS_TRDY_MSK) {
if (xmit->head == xmit->tail)
break;
writel(xmit->buf[xmit->tail],
port->membase + ALTERA_UART_TXDATA_REG);
xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
port->icount.tx++;
}
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
uart_write_wakeup(port);
if (xmit->head == xmit->tail) {
pp->imr &= ~ALTERA_UART_CONTROL_TRDY_MSK;
writel(pp->imr, port->membase + ALTERA_UART_CONTROL_REG);
}
}
static irqreturn_t altera_uart_interrupt(int irq, void *data)
{
struct uart_port *port = data;
struct altera_uart *pp = container_of(port, struct altera_uart, port);
unsigned int isr;
isr = readl(port->membase + ALTERA_UART_STATUS_REG) & pp->imr;
if (isr & ALTERA_UART_STATUS_RRDY_MSK)
altera_uart_rx_chars(pp);
if (isr & ALTERA_UART_STATUS_TRDY_MSK)
altera_uart_tx_chars(pp);
return IRQ_RETVAL(isr);
}
static void altera_uart_config_port(struct uart_port *port, int flags)
{
port->type = PORT_ALTERA_UART;
/* Clear mask, so no surprise interrupts. */
writel(0, port->membase + ALTERA_UART_CONTROL_REG);
/* Clear status register */
writel(0, port->membase + ALTERA_UART_STATUS_REG);
}
static int altera_uart_startup(struct uart_port *port)
{
struct altera_uart *pp = container_of(port, struct altera_uart, port);
unsigned long flags;
int ret;
ret = request_irq(port->irq, altera_uart_interrupt, IRQF_DISABLED,
DRV_NAME, port);
if (ret) {
pr_err(DRV_NAME ": unable to attach Altera UART %d "
"interrupt vector=%d\n", port->line, port->irq);
return ret;
}
spin_lock_irqsave(&port->lock, flags);
/* Enable RX interrupts now */
pp->imr = ALTERA_UART_CONTROL_RRDY_MSK;
writel(pp->imr, port->membase + ALTERA_UART_CONTROL_REG);
spin_unlock_irqrestore(&port->lock, flags);
return 0;
}
static void altera_uart_shutdown(struct uart_port *port)
{
struct altera_uart *pp = container_of(port, struct altera_uart, port);
unsigned long flags;
spin_lock_irqsave(&port->lock, flags);
/* Disable all interrupts now */
pp->imr = 0;
writel(pp->imr, port->membase + ALTERA_UART_CONTROL_REG);
spin_unlock_irqrestore(&port->lock, flags);
free_irq(port->irq, port);
}
static const char *altera_uart_type(struct uart_port *port)
{
return (port->type == PORT_ALTERA_UART) ? "Altera UART" : NULL;
}
static int altera_uart_request_port(struct uart_port *port)
{
/* UARTs always present */
return 0;
}
static void altera_uart_release_port(struct uart_port *port)
{
/* Nothing to release... */
}
static int altera_uart_verify_port(struct uart_port *port,
struct serial_struct *ser)
{
if ((ser->type != PORT_UNKNOWN) && (ser->type != PORT_ALTERA_UART))
return -EINVAL;
return 0;
}
/*
* Define the basic serial functions we support.
*/
static struct uart_ops altera_uart_ops = {
.tx_empty = altera_uart_tx_empty,
.get_mctrl = altera_uart_get_mctrl,
.set_mctrl = altera_uart_set_mctrl,
.start_tx = altera_uart_start_tx,
.stop_tx = altera_uart_stop_tx,
.stop_rx = altera_uart_stop_rx,
.enable_ms = altera_uart_enable_ms,
.break_ctl = altera_uart_break_ctl,
.startup = altera_uart_startup,
.shutdown = altera_uart_shutdown,
.set_termios = altera_uart_set_termios,
.type = altera_uart_type,
.request_port = altera_uart_request_port,
.release_port = altera_uart_release_port,
.config_port = altera_uart_config_port,
.verify_port = altera_uart_verify_port,
};
static struct altera_uart altera_uart_ports[CONFIG_SERIAL_ALTERA_UART_MAXPORTS];
#if defined(CONFIG_SERIAL_ALTERA_UART_CONSOLE)
int __init early_altera_uart_setup(struct altera_uart_platform_uart *platp)
{
struct uart_port *port;
int i;
for (i = 0; i < CONFIG_SERIAL_ALTERA_UART_MAXPORTS && platp[i].mapbase; i++) {
port = &altera_uart_ports[i].port;
port->line = i;
port->type = PORT_ALTERA_UART;
port->mapbase = platp[i].mapbase;
port->membase = ioremap(port->mapbase, ALTERA_UART_SIZE);
port->iotype = SERIAL_IO_MEM;
port->irq = platp[i].irq;
port->uartclk = platp[i].uartclk;
port->flags = ASYNC_BOOT_AUTOCONF;
port->ops = &altera_uart_ops;
}
return 0;
}
static void altera_uart_console_putc(struct console *co, const char c)
{
struct uart_port *port = &(altera_uart_ports + co->index)->port;
int i;
for (i = 0; i < 0x10000; i++) {
if (readl(port->membase + ALTERA_UART_STATUS_REG) &
ALTERA_UART_STATUS_TRDY_MSK)
break;
}
writel(c, port->membase + ALTERA_UART_TXDATA_REG);
for (i = 0; i < 0x10000; i++) {
if (readl(port->membase + ALTERA_UART_STATUS_REG) &
ALTERA_UART_STATUS_TRDY_MSK)
break;
}
}
static void altera_uart_console_write(struct console *co, const char *s,
unsigned int count)
{
for (; count; count--, s++) {
altera_uart_console_putc(co, *s);
if (*s == '\n')
altera_uart_console_putc(co, '\r');
}
}
static int __init altera_uart_console_setup(struct console *co, char *options)
{
struct uart_port *port;
int baud = CONFIG_SERIAL_ALTERA_UART_BAUDRATE;
int bits = 8;
int parity = 'n';
int flow = 'n';
if (co->index < 0 || co->index >= CONFIG_SERIAL_ALTERA_UART_MAXPORTS)
return -EINVAL;
port = &altera_uart_ports[co->index].port;
if (port->membase == 0)
return -ENODEV;
if (options)
uart_parse_options(options, &baud, &parity, &bits, &flow);
return uart_set_options(port, co, baud, parity, bits, flow);
}
static struct uart_driver altera_uart_driver;
static struct console altera_uart_console = {
.name = "ttyS",
.write = altera_uart_console_write,
.device = uart_console_device,
.setup = altera_uart_console_setup,
.flags = CON_PRINTBUFFER,
.index = -1,
.data = &altera_uart_driver,
};
static int __init altera_uart_console_init(void)
{
register_console(&altera_uart_console);
return 0;
}
console_initcall(altera_uart_console_init);
#define ALTERA_UART_CONSOLE (&altera_uart_console)
#else
#define ALTERA_UART_CONSOLE NULL
#endif /* CONFIG_ALTERA_UART_CONSOLE */
/*
* Define the altera_uart UART driver structure.
*/
static struct uart_driver altera_uart_driver = {
.owner = THIS_MODULE,
.driver_name = DRV_NAME,
.dev_name = "ttyS",
.major = TTY_MAJOR,
.minor = 64,
.nr = CONFIG_SERIAL_ALTERA_UART_MAXPORTS,
.cons = ALTERA_UART_CONSOLE,
};
static int __devinit altera_uart_probe(struct platform_device *pdev)
{
struct altera_uart_platform_uart *platp = pdev->dev.platform_data;
struct uart_port *port;
int i;
for (i = 0; i < CONFIG_SERIAL_ALTERA_UART_MAXPORTS && platp[i].mapbase; i++) {
port = &altera_uart_ports[i].port;
port->line = i;
port->type = PORT_ALTERA_UART;
port->mapbase = platp[i].mapbase;
port->membase = ioremap(port->mapbase, ALTERA_UART_SIZE);
port->iotype = SERIAL_IO_MEM;
port->irq = platp[i].irq;
port->uartclk = platp[i].uartclk;
port->ops = &altera_uart_ops;
port->flags = ASYNC_BOOT_AUTOCONF;
uart_add_one_port(&altera_uart_driver, port);
}
return 0;
}
static int altera_uart_remove(struct platform_device *pdev)
{
struct uart_port *port;
int i;
for (i = 0; i < CONFIG_SERIAL_ALTERA_UART_MAXPORTS; i++) {
port = &altera_uart_ports[i].port;
if (port)
uart_remove_one_port(&altera_uart_driver, port);
}
return 0;
}
static struct platform_driver altera_uart_platform_driver = {
.probe = altera_uart_probe,
.remove = __devexit_p(altera_uart_remove),
.driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
.pm = NULL,
},
};
static int __init altera_uart_init(void)
{
int rc;
rc = uart_register_driver(&altera_uart_driver);
if (rc)
return rc;
rc = platform_driver_register(&altera_uart_platform_driver);
if (rc) {
uart_unregister_driver(&altera_uart_driver);
return rc;
}
return 0;
}
static void __exit altera_uart_exit(void)
{
platform_driver_unregister(&altera_uart_platform_driver);
uart_unregister_driver(&altera_uart_driver);
}
module_init(altera_uart_init);
module_exit(altera_uart_exit);
MODULE_DESCRIPTION("Altera UART driver");
MODULE_AUTHOR("Thomas Chou <thomas@wytron.com.tw>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" DRV_NAME);

View File

@ -0,0 +1,14 @@
/*
* altera_uart.h -- Altera UART driver defines.
*/
#ifndef __ALTUART_H
#define __ALTUART_H
struct altera_uart_platform_uart {
unsigned long mapbase; /* Physical address base */
unsigned int irq; /* Interrupt vector */
unsigned int uartclk; /* UART clock rate */
};
#endif /* __ALTUART_H */

View File

@ -184,6 +184,7 @@
/* Altera UARTs */
#define PORT_ALTERA_JTAGUART 91
#define PORT_ALTERA_UART 92
#ifdef __KERNEL__