SERIAL: Added Timberdale UART driver
PATCH 8/9 SERIAL: Added Timberdale UART driver
Driver for the UART found in the Timberdale FPGA
Signed-off-by Richard Röjfors <[email protected]>
---
drivers/serial/timbuart.c +520 -0
@@ -0,0 +1,520 @@ +/* +* timbuart.c timberdale FPGA UART driver +* Copyright (c) 2009 Intel Corporation +* +* 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. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* Supports: +* Timberdale FPGA UART +*/ + +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/serial_core.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/ioport.h> + +#include "timbuart.h" + +struct timbuart_port { +struct uart_port port; +struct tasklet_struct tasklet; +int usedma; +u8 last_ier; +struct platform_device *dev; +}; + +static int baudrates[] = {9600 ,19200 ,38400 ,57600 ,115200 ,230400 ,460800 , +921600 ,1843200 ,3250000 }; + +static void timbuart_mctrl_check (struct uart_port *port,u8 isr,u8 *ier); + +static irqreturn_t timbuart_handleinterrupt (int irq,void *devid); + +static void timbuart_stop_rx (struct uart_port *port) +{ +/* spin lock held by upper layer, disable all RX interrupts */ +u8 ier =ioread8 (port->membase +TIMBUART_IER ) & ~RXFLAGS ; +iowrite8 (ier, port->membase +TIMBUART_IER ); +} + +static void timbuart_stop_tx (struct uart_port *port) +{ +/* spinlock held by upper layer, disable TX interrupt */ +u8 ier =ioread8 (port->membase +TIMBUART_IER ) & ~TXBAE ; +iowrite8 (ier, port->membase +TIMBUART_IER ); +} + +static void timbuart_start_tx (struct uart_port *port) +{ +struct timbuart_port *uart = + container_of(port, struct timbuart_port, port); + +/* do not transfer anything here -> fire off the tasklet */ +tasklet_schedule (&uart->tasklet); +} + +static void timbuart_flush_buffer (struct uart_port *port) +{ +u8 ctl =ioread8 (port->membase +TIMBUART_CTRL ) |TIMBUART_CTRL_FLSHTX ; + +iowrite8 (ctl, port->membase +TIMBUART_CTRL ); +iowrite8 (TXBF , port->membase +TIMBUART_ISR ); +} + +static void timbuart_rx_chars (struct uart_port *port) +{ +struct tty_struct *tty = port->info->port.tty; + +while (ioread8 (port->membase +TIMBUART_ISR ) &RXDP ) { +u8 ch =ioread8 (port->membase +TIMBUART_RXFIFO ); + port->icount.rx++; +tty_insert_flip_char (tty, ch,TTY_NORMAL ); + } + +spin_unlock (&port->lock); +tty_flip_buffer_push (port->info->port.tty); +spin_lock (&port->lock); + +dev_dbg (port->dev,"%s - total read %d bytes\n" , + __func__, port->icount.rx); +} + +static void timbuart_tx_chars (struct uart_port *port) +{ +struct circ_buf *xmit = &port->info->xmit; + +while (!(ioread8 (port->membase +TIMBUART_ISR ) &TXBF ) && + !uart_circ_empty (xmit)) { +iowrite8 (xmit->buf[xmit->tail], + port->membase +TIMBUART_TXFIFO ); + xmit->tail = (xmit->tail +1 ) & (UART_XMIT_SIZE -1 ); + port->icount.tx++; + } + +dev_dbg (port->dev, +"%s - total written %d bytes, CTL: %x, RTS: %x, baud: %x\n" , + __func__, + port->icount.tx, +ioread8 (port->membase +TIMBUART_CTRL ), + port->mctrl &TIOCM_RTS , +ioread8 (port->membase +TIMBUART_BAUDRATE )); +} + +static void timbuart_handle_tx_port (struct uart_port *port,u8 isr,u8 *ier) +{ +struct timbuart_port *uart = + container_of(port, struct timbuart_port, port); +struct circ_buf *xmit = &port->info->xmit; + +if (uart_circ_empty (xmit) ||uart_tx_stopped (port)) +return ; + +if (port->x_char) +return ; + +if (isr &TXFLAGS ) { +timbuart_tx_chars (port); +/* clear all TX interrupts */ +iowrite8 (TXFLAGS , port->membase +TIMBUART_ISR ); + +if (uart_circ_chars_pending (xmit) <WAKEUP_CHARS ) +uart_write_wakeup (port); + }else +/* Re-enable any tx interrupt */ + *ier |= uart->last_ier &TXFLAGS ; + +/* enable interrupts if there are chars in the transmit buffer, +* Or if we delivered some bytes and want the almost empty interrupt +* we wake up the upper layer later when we got the interrupt +* to give it some time to go out... +*/ +if (!uart_circ_empty (xmit)) + *ier |=TXBAE ; + +dev_dbg (port->dev,"%s - leaving\n" , __func__); +} + +void timbuart_handle_rx_port (struct uart_port *port,u8 isr,u8 *ier) +{ +if (isr &RXFLAGS ) { +/* Some RX status is set */ +if (isr &RXBF ) { +u8 ctl =ioread8 (port->membase +TIMBUART_CTRL ) | +TIMBUART_CTRL_FLSHRX ; +iowrite8 (ctl, port->membase +TIMBUART_CTRL ); + port->icount.overrun++; + }else if (isr & (RXDP )) +timbuart_rx_chars (port); + +/* ack all RX interrupts */ +iowrite8 (RXFLAGS , port->membase +TIMBUART_ISR ); + } + +/* always have the RX interrupts enabled */ + *ier |=RXBAF |RXBF |RXTT ; + +dev_dbg (port->dev,"%s - leaving\n" , __func__); +} + +void timbuart_tasklet (unsigned long arg) +{ +struct timbuart_port *uart = (struct timbuart_port *)arg; +u8 isr, ier =0 ; + +spin_lock (&uart->port.lock); + + isr =ioread8 (uart->port.membase +TIMBUART_ISR ); +dev_dbg (uart->port.dev,"%s ISR: %x\n" , __func__, isr); + +if (!uart->usedma) +timbuart_handle_tx_port (&uart->port, isr, &ier); + +timbuart_mctrl_check (&uart->port, isr, &ier); + +if (!uart->usedma) +timbuart_handle_rx_port (&uart->port, isr, &ier); + +iowrite8 (ier, uart->port.membase +TIMBUART_IER ); + +spin_unlock (&uart->port.lock); +dev_dbg (uart->port.dev,"%s leaving\n" , __func__); +} + +static unsigned int timbuart_tx_empty (struct uart_port *port) +{ +u8 isr =ioread8 (port->membase +TIMBUART_ISR ); + +return (isr &TXBAE ) ?TIOCSER_TEMT :0 ; +} + +static unsigned int timbuart_get_mctrl (struct uart_port *port) +{ +u8 cts =ioread8 (port->membase +TIMBUART_CTRL ); +dev_dbg (port->dev,"%s - cts %x\n" , __func__, cts); + +if (cts &TIMBUART_CTRL_CTS ) +return TIOCM_CTS |TIOCM_DSR |TIOCM_CAR ; +else +return TIOCM_DSR |TIOCM_CAR ; +} + +static void timbuart_set_mctrl (struct uart_port *port,unsigned int mctrl) +{ +dev_dbg (port->dev,"%s - %x\n" , __func__, mctrl); + +if (mctrl &TIOCM_RTS ) +iowrite8 (TIMBUART_CTRL_RTS , port->membase +TIMBUART_CTRL ); +else +iowrite8 (TIMBUART_CTRL_RTS , port->membase +TIMBUART_CTRL ); +} + +static void timbuart_mctrl_check (struct uart_port *port,u8 isr,u8 *ier) +{ +unsigned int cts; + +if (isr &CTS_DELTA ) { +/* ack */ +iowrite8 (CTS_DELTA , port->membase +TIMBUART_ISR ); + cts =timbuart_get_mctrl (port); +uart_handle_cts_change (port, cts &TIOCM_CTS ); +wake_up_interruptible (&port->info->delta_msr_wait); + } + + *ier |=CTS_DELTA ; +} + +static void timbuart_enable_ms (struct uart_port *port) +{ +/* N/A */ +} + +static void timbuart_break_ctl (struct uart_port *port,int ctl) +{ +/* N/A */ +} + +static int timbuart_startup (struct uart_port *port) +{ +struct timbuart_port *uart = + container_of(port, struct timbuart_port, port); + +dev_dbg (port->dev,"%s\n" , __func__); + +iowrite8 (TIMBUART_CTRL_FLSHRX , port->membase +TIMBUART_CTRL ); +iowrite8 (0xff , port->membase +TIMBUART_ISR ); +/* Enable all but TX interrupts */ +iowrite8 (RXBAF |RXBF |RXTT |CTS_DELTA , + port->membase +TIMBUART_IER ); + +return request_irq (port->irq, timbuart_handleinterrupt,IRQF_SHARED , +"timb-uart" , uart); +} + +static void timbuart_shutdown (struct uart_port *port) +{ +struct timbuart_port *uart = + container_of(port, struct timbuart_port, port); +dev_dbg (port->dev,"%s\n" , __func__); +free_irq (port->irq, uart); +iowrite8 (0 , port->membase +TIMBUART_IER ); +} + +static int get_bindex (int baud) +{ +int i; + +for (i =0 ; i <ARRAY_SIZE (baudrates); i++) +if (baud == baudrates[i]) +return i; + +return -1 ; +} + +static void timbuart_set_termios (struct uart_port *port, +struct ktermios *termios, +struct ktermios *old) +{ +unsigned int baud; +short bindex; +unsigned long flags; + + baud =uart_get_baud_rate (port, termios, old,0 , port->uartclk /16 ); + bindex =get_bindex (baud); +dev_dbg (port->dev,"%s - bindex %d\n" , __func__, bindex); + +if (bindex <0 ) { +printk (KERN_ALERT "timbuart: Unsupported baud rate\n" ); + }else { +spin_lock_irqsave (&port->lock, flags); +iowrite8 ((u8 )bindex, port->membase +TIMBUART_BAUDRATE ); +uart_update_timeout (port, termios->c_cflag, baud); +spin_unlock_irqrestore (&port->lock, flags); + } +} + +static const char *timbuart_type (struct uart_port *port) +{ +return port->type ==PORT_UNKNOWN ?"timbuart" :NULL ; +} + +/* We do not request/release mappings of the registers here, +* currently it's done in the proble function. +*/ +static void timbuart_release_port (struct uart_port *port) +{ +struct platform_device *pdev =to_platform_device (port->dev); +int size = +resource_size (platform_get_resource (pdev,IORESOURCE_MEM ,0 )); + +if (port->flags &UPF_IOREMAP ) { +iounmap (port->membase); + port->membase =NULL ; + } + +release_mem_region (port->mapbase, size); +} + +static int timbuart_request_port (struct uart_port *port) +{ +struct platform_device *pdev =to_platform_device (port->dev); +int size = +resource_size (platform_get_resource (pdev,IORESOURCE_MEM ,0 )); + +if (!request_mem_region (port->mapbase, size,"timb-uart" )) +return -EBUSY ; + +if (port->flags &UPF_IOREMAP ) { + port->membase =ioremap (port->mapbase, size); +if (port->membase ==NULL ) { +release_mem_region (port->mapbase, size); +return -ENOMEM ; + } + } + +return 0 ; +} + +static irqreturn_t timbuart_handleinterrupt (int irq,void *devid) +{ +struct timbuart_port *uart = (struct timbuart_port *)devid; + +if (ioread8 (uart->port.membase +TIMBUART_IPR )) { + uart->last_ier =ioread8 (uart->port.membase +TIMBUART_IER ); + +/* disable interrupts, the tasklet enables them again */ +iowrite8 (0 , uart->port.membase +TIMBUART_IER ); + +/* fire off bottom half */ +tasklet_schedule (&uart->tasklet); + +return IRQ_HANDLED ; + }else +return IRQ_NONE ; +} + +/* +* Configure/autoconfigure the port. +*/ +static void timbuart_config_port (struct uart_port *port,int flags) +{ +if (flags &UART_CONFIG_TYPE ) { + port->type =PORT_TIMBUART ; +timbuart_request_port (port); + } +} + +static int timbuart_verify_port (struct uart_port *port, +struct serial_struct *ser) +{ +/* we don't want the core code to modify any port params */ +return -EINVAL ; +} + +static struct uart_ops timbuart_ops = { + .tx_empty = timbuart_tx_empty, + .set_mctrl = timbuart_set_mctrl, + .get_mctrl = timbuart_get_mctrl, + .stop_tx = timbuart_stop_tx, + .start_tx = timbuart_start_tx, + .flush_buffer = timbuart_flush_buffer, + .stop_rx = timbuart_stop_rx, + .enable_ms = timbuart_enable_ms, + .break_ctl = timbuart_break_ctl, + .startup = timbuart_startup, + .shutdown = timbuart_shutdown, + .set_termios = timbuart_set_termios, + .type = timbuart_type, + .release_port = timbuart_release_port, + .request_port = timbuart_request_port, + .config_port = timbuart_config_port, + .verify_port = timbuart_verify_port +}; + +static struct uart_driver timbuart_driver = { + .owner =THIS_MODULE , + .driver_name ="timberdale_uart" , + .dev_name ="ttyTU" , + .major =TIMBUART_MAJOR , + .minor =TIMBUART_MINOR , + .nr =1 +}; + +static int timbuart_probe (struct platform_device *dev) +{ +int err; +struct timbuart_port *uart; +struct resource *iomem; + +dev_dbg (&dev->dev,"%s\n" , __func__); + + uart =kzalloc (sizeof (*uart),GFP_KERNEL ); +if (!uart) { + err = -EINVAL ; + goto err_mem; + } + + uart->usedma =0 ; + + uart->port.uartclk =3250000 *16 ; + uart->port.fifosize =TIMBUART_FIFO_SIZE ; + uart->port.regshift =2 ; + uart->port.iotype =UPIO_MEM ; + uart->port.ops = &timbuart_ops; + uart->port.irq =0 ; + uart->port.flags =UPF_BOOT_AUTOCONF |UPF_IOREMAP ; + uart->port.line =0 ; + uart->port.dev = &dev->dev; + + iomem =platform_get_resource (dev,IORESOURCE_MEM ,0 ); +if (!iomem) { + err = -ENOMEM ; + goto err_register; + } + uart->port.mapbase = iomem->start; + uart->port.membase =NULL ; + + uart->port.irq =platform_get_irq (dev,0 ); +if (uart->port.irq <0 ) { + err = -EINVAL ; + goto err_register; + } + +tasklet_init (&uart->tasklet, timbuart_tasklet, (unsigned long )uart); + + err =uart_register_driver (&timbuart_driver); +if (err) + goto err_register; + + err =uart_add_one_port (&timbuart_driver, &uart->port); +if (err) + goto err_add_port; + +platform_set_drvdata (dev, uart); + +return 0 ; + +err_add_port: +uart_unregister_driver (&timbuart_driver); +err_register: +kfree (uart); +err_mem: +printk (KERN_ERR "timberdale: Failed to register Timberdale UART: %d\n" , + err); + +return err; +} + +static int timbuart_remove (struct platform_device *dev) +{ +struct timbuart_port *uart =platform_get_drvdata (dev); + +tasklet_kill (&uart->tasklet); +uart_remove_one_port (&timbuart_driver, &uart->port); +uart_unregister_driver (&timbuart_driver); +kfree (uart); + +return 0 ; +} + +static struct platform_driver timbuart_platform_driver = { + .driver = { + .name ="timb-uart" , + .owner =THIS_MODULE , + }, + .probe = timbuart_probe, + .remove = timbuart_remove, +}; + +/*--------------------------------------------------------------------------*/ + +static int __init timbuart_init (void ) +{ +return platform_driver_register (&timbuart_platform_driver); +} + +static void __exit timbuart_exit(void) +{ +platform_driver_unregister (&timbuart_platform_driver); +} + +module_init (timbuart_init); +module_exit (timbuart_exit); + +MODULE_DESCRIPTION ("Timberdale UART driver" ); +MODULE_LICENSE ("GPL v2" ); +MODULE_ALIAS ("platform:timb-uart" ); +
drivers/serial/timbuart.h +58 -0
58 lines not on this page · raw message
drivers/serial/Makefile +1 -0
@@ -77,3 +77,4 @@ obj-$(CONFIG_SERIAL_KS8695) += serial_ks8695.o obj-$(CONFIG_KGDB_SERIAL_CONSOLE) += kgdboc.o obj-$(CONFIG_SERIAL_QE) += ucc_uart.o +obj-$(CONFIG_SERIAL_TIMBERDALE) += timbuart.o
drivers/serial/Kconfig +7 -0
@@ -1433,4 +1433,11 @@ default 19200 if (SERIAL_SPORT_BAUD_RATE_19200) default 9600 if (SERIAL_SPORT_BAUD_RATE_9600) +config SERIAL_TIMBERDALE + tristate "Support for timberdale UART" + depends on MFD_TIMBERDALE + select SERIAL_CORE + ---help--- + Add support for UART controller on timberdale. + endmenu
include/linux/serial_core.h +3 -0
@@ -167,6 +167,9 @@/* MAX3100 */ #define PORT_MAX3100 86 +/* Timberdale UART */ +#define PORT_TIMBUART 87 +#ifdef __KERNEL__#include <linux/compiler.h>