2005-04-17 02:20:36 +04:00
/* drivers/serial/serial_lh7a40x.c
*
* Copyright ( C ) 2004 Coastal Environmental Systems
*
* 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 .
*
*/
/* Driver for Sharp LH7A40X embedded serial ports
*
* Based on drivers / char / serial . c , by Linus Torvalds , Theodore Ts ' o .
* Based on drivers / serial / amba . c , by Deep Blue Solutions Ltd .
*
* - - -
*
* This driver supports the embedded UARTs of the Sharp LH7A40X series
* CPUs . While similar to the 16550 and other UART chips , there is
* nothing close to register compatibility . Moreover , some of the
* modem control lines are not available , either in the chip or they
* are lacking in the board - level implementation .
*
* - Use of SIRDIS
* For simplicity , we disable the IR functions of any UART whenever
* we enable it .
*
*/
# include <linux/config.h>
# if defined(CONFIG_SERIAL_LH7A40X_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)
# define SUPPORT_SYSRQ
# endif
# include <linux/module.h>
# include <linux/ioport.h>
# include <linux/init.h>
# include <linux/console.h>
# include <linux/sysrq.h>
# include <linux/tty.h>
# include <linux/tty_flip.h>
# include <linux/serial_core.h>
# include <linux/serial.h>
# include <asm/io.h>
# include <asm/irq.h>
# define DEV_MAJOR 204
# define DEV_MINOR 16
# define DEV_NR 3
# define ISR_LOOP_LIMIT 256
# define UR(p,o) _UR ((p)->membase, o)
# define _UR(b,o) (*((volatile unsigned int*)(((unsigned char*) b) + (o))))
# define BIT_CLR(p,o,m) UR(p,o) = UR(p,o) & (~(unsigned int)m)
# define BIT_SET(p,o,m) UR(p,o) = UR(p,o) | ( (unsigned int)m)
# define UART_REG_SIZE 32
# define UART_R_DATA (0x00)
# define UART_R_FCON (0x04)
# define UART_R_BRCON (0x08)
# define UART_R_CON (0x0c)
# define UART_R_STATUS (0x10)
# define UART_R_RAWISR (0x14)
# define UART_R_INTEN (0x18)
# define UART_R_ISR (0x1c)
# define UARTEN (0x01) /* UART enable */
# define SIRDIS (0x02) /* Serial IR disable (UART1 only) */
# define RxEmpty (0x10)
# define TxEmpty (0x80)
# define TxFull (0x20)
# define nRxRdy RxEmpty
# define nTxRdy TxFull
# define TxBusy (0x08)
# define RxBreak (0x0800)
# define RxOverrunError (0x0400)
# define RxParityError (0x0200)
# define RxFramingError (0x0100)
# define RxError (RxBreak | RxOverrunError | RxParityError | RxFramingError)
# define DCD (0x04)
# define DSR (0x02)
# define CTS (0x01)
# define RxInt (0x01)
# define TxInt (0x02)
# define ModemInt (0x04)
# define RxTimeoutInt (0x08)
# define MSEOI (0x10)
# define WLEN_8 (0x60)
# define WLEN_7 (0x40)
# define WLEN_6 (0x20)
# define WLEN_5 (0x00)
# define WLEN (0x60) /* Mask for all word-length bits */
# define STP2 (0x08)
# define PEN (0x02) /* Parity Enable */
# define EPS (0x04) /* Even Parity Set */
# define FEN (0x10) /* FIFO Enable */
# define BRK (0x01) /* Send Break */
struct uart_port_lh7a40x {
struct uart_port port ;
unsigned int statusPrev ; /* Most recently read modem status */
} ;
2005-08-31 13:12:14 +04:00
static void lh7a40xuart_stop_tx ( struct uart_port * port )
2005-04-17 02:20:36 +04:00
{
BIT_CLR ( port , UART_R_INTEN , TxInt ) ;
}
2005-08-31 13:12:14 +04:00
static void lh7a40xuart_start_tx ( struct uart_port * port )
2005-04-17 02:20:36 +04:00
{
BIT_SET ( port , UART_R_INTEN , TxInt ) ;
/* *** FIXME: do I need to check for startup of the
transmitter ? The old driver did , but AMBA
doesn ' t . */
}
static void lh7a40xuart_stop_rx ( struct uart_port * port )
{
BIT_SET ( port , UART_R_INTEN , RxTimeoutInt | RxInt ) ;
}
static void lh7a40xuart_enable_ms ( struct uart_port * port )
{
BIT_SET ( port , UART_R_INTEN , ModemInt ) ;
}
static void
# ifdef SUPPORT_SYSRQ
lh7a40xuart_rx_chars ( struct uart_port * port , struct pt_regs * regs )
# else
lh7a40xuart_rx_chars ( struct uart_port * port )
# endif
{
struct tty_struct * tty = port - > info - > tty ;
int cbRxMax = 256 ; /* (Gross) limit on receive */
unsigned int data , flag ; /* Received data and status */
while ( ! ( UR ( port , UART_R_STATUS ) & nRxRdy ) & & - - cbRxMax ) {
data = UR ( port , UART_R_DATA ) ;
flag = TTY_NORMAL ;
+ + port - > icount . rx ;
2005-04-26 18:29:44 +04:00
if ( unlikely ( data & RxError ) ) { /* Quick check, short-circuit */
2005-04-17 02:20:36 +04:00
if ( data & RxBreak ) {
data & = ~ ( RxFramingError | RxParityError ) ;
+ + port - > icount . brk ;
if ( uart_handle_break ( port ) )
continue ;
}
else if ( data & RxParityError )
+ + port - > icount . parity ;
else if ( data & RxFramingError )
+ + port - > icount . frame ;
if ( data & RxOverrunError )
+ + port - > icount . overrun ;
/* Mask by termios, leave Rx'd byte */
data & = port - > read_status_mask | 0xff ;
if ( data & RxBreak )
flag = TTY_BREAK ;
else if ( data & RxParityError )
flag = TTY_PARITY ;
else if ( data & RxFramingError )
flag = TTY_FRAME ;
}
if ( uart_handle_sysrq_char ( port , ( unsigned char ) data , regs ) )
continue ;
2005-05-10 02:21:59 +04:00
uart_insert_char ( port , data , RxOverrunError , data , flag ) ;
2005-04-17 02:20:36 +04:00
}
tty_flip_buffer_push ( tty ) ;
return ;
}
static void lh7a40xuart_tx_chars ( struct uart_port * port )
{
struct circ_buf * xmit = & port - > info - > xmit ;
int cbTxMax = port - > fifosize ;
if ( port - > x_char ) {
UR ( port , UART_R_DATA ) = port - > x_char ;
+ + port - > icount . tx ;
port - > x_char = 0 ;
return ;
}
if ( uart_circ_empty ( xmit ) | | uart_tx_stopped ( port ) ) {
2005-09-07 02:14:59 +04:00
lh7a40xuart_stop_tx ( port ) ;
2005-04-17 02:20:36 +04:00
return ;
}
/* Unlike the AMBA UART, the lh7a40x UART does not guarantee
that at least half of the FIFO is empty . Instead , we check
status for every character . Using the AMBA method causes
the transmitter to drop characters . */
do {
UR ( port , UART_R_DATA ) = xmit - > buf [ xmit - > tail ] ;
xmit - > tail = ( xmit - > tail + 1 ) & ( UART_XMIT_SIZE - 1 ) ;
+ + port - > icount . tx ;
if ( uart_circ_empty ( xmit ) )
break ;
} while ( ! ( UR ( port , UART_R_STATUS ) & nTxRdy )
& & cbTxMax - - ) ;
if ( uart_circ_chars_pending ( xmit ) < WAKEUP_CHARS )
uart_write_wakeup ( port ) ;
if ( uart_circ_empty ( xmit ) )
2005-09-07 02:14:59 +04:00
lh7a40xuart_stop_tx ( port ) ;
2005-04-17 02:20:36 +04:00
}
static void lh7a40xuart_modem_status ( struct uart_port * port )
{
unsigned int status = UR ( port , UART_R_STATUS ) ;
unsigned int delta
= status ^ ( ( struct uart_port_lh7a40x * ) port ) - > statusPrev ;
BIT_SET ( port , UART_R_RAWISR , MSEOI ) ; /* Clear modem status intr */
if ( ! delta ) /* Only happens if we missed 2 transitions */
return ;
( ( struct uart_port_lh7a40x * ) port ) - > statusPrev = status ;
if ( delta & DCD )
uart_handle_dcd_change ( port , status & DCD ) ;
if ( delta & DSR )
+ + port - > icount . dsr ;
if ( delta & CTS )
uart_handle_cts_change ( port , status & CTS ) ;
wake_up_interruptible ( & port - > info - > delta_msr_wait ) ;
}
static irqreturn_t lh7a40xuart_int ( int irq , void * dev_id ,
struct pt_regs * regs )
{
struct uart_port * port = dev_id ;
unsigned int cLoopLimit = ISR_LOOP_LIMIT ;
unsigned int isr = UR ( port , UART_R_ISR ) ;
do {
if ( isr & ( RxInt | RxTimeoutInt ) )
# ifdef SUPPORT_SYSRQ
lh7a40xuart_rx_chars ( port , regs ) ;
# else
lh7a40xuart_rx_chars ( port ) ;
# endif
if ( isr & ModemInt )
lh7a40xuart_modem_status ( port ) ;
if ( isr & TxInt )
lh7a40xuart_tx_chars ( port ) ;
if ( - - cLoopLimit = = 0 )
break ;
isr = UR ( port , UART_R_ISR ) ;
} while ( isr & ( RxInt | TxInt | RxTimeoutInt ) ) ;
return IRQ_HANDLED ;
}
static unsigned int lh7a40xuart_tx_empty ( struct uart_port * port )
{
return ( UR ( port , UART_R_STATUS ) & TxEmpty ) ? TIOCSER_TEMT : 0 ;
}
static unsigned int lh7a40xuart_get_mctrl ( struct uart_port * port )
{
unsigned int result = 0 ;
unsigned int status = UR ( port , UART_R_STATUS ) ;
if ( status & DCD )
result | = TIOCM_CAR ;
if ( status & DSR )
result | = TIOCM_DSR ;
if ( status & CTS )
result | = TIOCM_CTS ;
return result ;
}
static void lh7a40xuart_set_mctrl ( struct uart_port * port , unsigned int mctrl )
{
/* None of the ports supports DTR. UART1 supports RTS through GPIO. */
/* Note, kernel appears to be setting DTR and RTS on console. */
/* *** FIXME: this deserves more work. There's some work in
tracing all of the IO pins . */
#if 0
if ( port - > mapbase = = UART1_PHYS ) {
gpioRegs_t * gpio = ( gpioRegs_t * ) IO_ADDRESS ( GPIO_PHYS ) ;
if ( mctrl & TIOCM_RTS )
gpio - > pbdr & = ~ GPIOB_UART1_RTS ;
else
gpio - > pbdr | = GPIOB_UART1_RTS ;
}
# endif
}
static void lh7a40xuart_break_ctl ( struct uart_port * port , int break_state )
{
unsigned long flags ;
spin_lock_irqsave ( & port - > lock , flags ) ;
if ( break_state = = - 1 )
BIT_SET ( port , UART_R_FCON , BRK ) ; /* Assert break */
else
BIT_CLR ( port , UART_R_FCON , BRK ) ; /* Deassert break */
spin_unlock_irqrestore ( & port - > lock , flags ) ;
}
static int lh7a40xuart_startup ( struct uart_port * port )
{
int retval ;
retval = request_irq ( port - > irq , lh7a40xuart_int , 0 ,
" serial_lh7a40x " , port ) ;
if ( retval )
return retval ;
/* Initial modem control-line settings */
( ( struct uart_port_lh7a40x * ) port ) - > statusPrev
= UR ( port , UART_R_STATUS ) ;
/* There is presently no configuration option to enable IR.
Thus , we always disable it . */
BIT_SET ( port , UART_R_CON , UARTEN | SIRDIS ) ;
BIT_SET ( port , UART_R_INTEN , RxTimeoutInt | RxInt ) ;
return 0 ;
}
static void lh7a40xuart_shutdown ( struct uart_port * port )
{
free_irq ( port - > irq , port ) ;
BIT_CLR ( port , UART_R_FCON , BRK | FEN ) ;
BIT_CLR ( port , UART_R_CON , UARTEN ) ;
}
static void lh7a40xuart_set_termios ( struct uart_port * port ,
struct termios * termios ,
struct termios * old )
{
unsigned int con ;
unsigned int inten ;
unsigned int fcon ;
unsigned long flags ;
unsigned int baud ;
unsigned int quot ;
baud = uart_get_baud_rate ( port , termios , old , 8 , port - > uartclk / 16 ) ;
quot = uart_get_divisor ( port , baud ) ; /* -1 performed elsewhere */
switch ( termios - > c_cflag & CSIZE ) {
case CS5 :
fcon = WLEN_5 ;
break ;
case CS6 :
fcon = WLEN_6 ;
break ;
case CS7 :
fcon = WLEN_7 ;
break ;
case CS8 :
default :
fcon = WLEN_8 ;
break ;
}
if ( termios - > c_cflag & CSTOPB )
fcon | = STP2 ;
if ( termios - > c_cflag & PARENB ) {
fcon | = PEN ;
if ( ! ( termios - > c_cflag & PARODD ) )
fcon | = EPS ;
}
if ( port - > fifosize > 1 )
fcon | = FEN ;
spin_lock_irqsave ( & port - > lock , flags ) ;
uart_update_timeout ( port , termios - > c_cflag , baud ) ;
port - > read_status_mask = RxOverrunError ;
if ( termios - > c_iflag & INPCK )
port - > read_status_mask | = RxFramingError | RxParityError ;
if ( termios - > c_iflag & ( BRKINT | PARMRK ) )
port - > read_status_mask | = RxBreak ;
/* Figure mask for status we ignore */
port - > ignore_status_mask = 0 ;
if ( termios - > c_iflag & IGNPAR )
port - > ignore_status_mask | = RxFramingError | RxParityError ;
if ( termios - > c_iflag & IGNBRK ) {
port - > ignore_status_mask | = RxBreak ;
/* Ignore overrun when ignorning parity */
/* *** FIXME: is this in the right place? */
if ( termios - > c_iflag & IGNPAR )
port - > ignore_status_mask | = RxOverrunError ;
}
/* Ignore all receive errors when receive disabled */
if ( ( termios - > c_cflag & CREAD ) = = 0 )
port - > ignore_status_mask | = RxError ;
con = UR ( port , UART_R_CON ) ;
inten = ( UR ( port , UART_R_INTEN ) & ~ ModemInt ) ;
if ( UART_ENABLE_MS ( port , termios - > c_cflag ) )
inten | = ModemInt ;
BIT_CLR ( port , UART_R_CON , UARTEN ) ; /* Disable UART */
UR ( port , UART_R_INTEN ) = 0 ; /* Disable interrupts */
UR ( port , UART_R_BRCON ) = quot - 1 ; /* Set baud rate divisor */
UR ( port , UART_R_FCON ) = fcon ; /* Set FIFO and frame ctrl */
UR ( port , UART_R_INTEN ) = inten ; /* Enable interrupts */
UR ( port , UART_R_CON ) = con ; /* Restore UART mode */
spin_unlock_irqrestore ( & port - > lock , flags ) ;
}
static const char * lh7a40xuart_type ( struct uart_port * port )
{
return port - > type = = PORT_LH7A40X ? " LH7A40X " : NULL ;
}
static void lh7a40xuart_release_port ( struct uart_port * port )
{
release_mem_region ( port - > mapbase , UART_REG_SIZE ) ;
}
static int lh7a40xuart_request_port ( struct uart_port * port )
{
return request_mem_region ( port - > mapbase , UART_REG_SIZE ,
" serial_lh7a40x " ) ! = NULL
? 0 : - EBUSY ;
}
static void lh7a40xuart_config_port ( struct uart_port * port , int flags )
{
if ( flags & UART_CONFIG_TYPE ) {
port - > type = PORT_LH7A40X ;
lh7a40xuart_request_port ( port ) ;
}
}
static int lh7a40xuart_verify_port ( struct uart_port * port ,
struct serial_struct * ser )
{
int ret = 0 ;
if ( ser - > type ! = PORT_UNKNOWN & & ser - > type ! = PORT_LH7A40X )
ret = - EINVAL ;
if ( ser - > irq < 0 | | ser - > irq > = NR_IRQS )
ret = - EINVAL ;
if ( ser - > baud_base < 9600 ) /* *** FIXME: is this true? */
ret = - EINVAL ;
return ret ;
}
static struct uart_ops lh7a40x_uart_ops = {
. tx_empty = lh7a40xuart_tx_empty ,
. set_mctrl = lh7a40xuart_set_mctrl ,
. get_mctrl = lh7a40xuart_get_mctrl ,
. stop_tx = lh7a40xuart_stop_tx ,
. start_tx = lh7a40xuart_start_tx ,
. stop_rx = lh7a40xuart_stop_rx ,
. enable_ms = lh7a40xuart_enable_ms ,
. break_ctl = lh7a40xuart_break_ctl ,
. startup = lh7a40xuart_startup ,
. shutdown = lh7a40xuart_shutdown ,
. set_termios = lh7a40xuart_set_termios ,
. type = lh7a40xuart_type ,
. release_port = lh7a40xuart_release_port ,
. request_port = lh7a40xuart_request_port ,
. config_port = lh7a40xuart_config_port ,
. verify_port = lh7a40xuart_verify_port ,
} ;
static struct uart_port_lh7a40x lh7a40x_ports [ DEV_NR ] = {
{
. port = {
. membase = ( void * ) io_p2v ( UART1_PHYS ) ,
. mapbase = UART1_PHYS ,
. iotype = SERIAL_IO_MEM ,
. irq = IRQ_UART1INTR ,
. uartclk = 14745600 / 2 ,
. fifosize = 16 ,
. ops = & lh7a40x_uart_ops ,
2006-01-21 22:28:15 +03:00
. flags = UPF_BOOT_AUTOCONF ,
2005-04-17 02:20:36 +04:00
. line = 0 ,
} ,
} ,
{
. port = {
. membase = ( void * ) io_p2v ( UART2_PHYS ) ,
. mapbase = UART2_PHYS ,
. iotype = SERIAL_IO_MEM ,
. irq = IRQ_UART2INTR ,
. uartclk = 14745600 / 2 ,
. fifosize = 16 ,
. ops = & lh7a40x_uart_ops ,
2006-01-21 22:28:15 +03:00
. flags = UPF_BOOT_AUTOCONF ,
2005-04-17 02:20:36 +04:00
. line = 1 ,
} ,
} ,
{
. port = {
. membase = ( void * ) io_p2v ( UART3_PHYS ) ,
. mapbase = UART3_PHYS ,
. iotype = SERIAL_IO_MEM ,
. irq = IRQ_UART3INTR ,
. uartclk = 14745600 / 2 ,
. fifosize = 16 ,
. ops = & lh7a40x_uart_ops ,
2006-01-21 22:28:15 +03:00
. flags = UPF_BOOT_AUTOCONF ,
2005-04-17 02:20:36 +04:00
. line = 2 ,
} ,
} ,
} ;
# ifndef CONFIG_SERIAL_LH7A40X_CONSOLE
# define LH7A40X_CONSOLE NULL
# else
# define LH7A40X_CONSOLE &lh7a40x_console
static void lh7a40xuart_console_write ( struct console * co ,
const char * s ,
unsigned int count )
{
struct uart_port * port = & lh7a40x_ports [ co - > index ] . port ;
unsigned int con = UR ( port , UART_R_CON ) ;
unsigned int inten = UR ( port , UART_R_INTEN ) ;
UR ( port , UART_R_INTEN ) = 0 ; /* Disable all interrupts */
BIT_SET ( port , UART_R_CON , UARTEN | SIRDIS ) ; /* Enable UART */
for ( ; count - - > 0 ; + + s ) {
while ( UR ( port , UART_R_STATUS ) & nTxRdy )
;
UR ( port , UART_R_DATA ) = * s ;
if ( * s = = ' \n ' ) {
while ( ( UR ( port , UART_R_STATUS ) & TxBusy ) )
;
UR ( port , UART_R_DATA ) = ' \r ' ;
}
}
/* Wait until all characters are sent */
while ( UR ( port , UART_R_STATUS ) & TxBusy )
;
/* Restore control and interrupt mask */
UR ( port , UART_R_CON ) = con ;
UR ( port , UART_R_INTEN ) = inten ;
}
static void __init lh7a40xuart_console_get_options ( struct uart_port * port ,
int * baud ,
int * parity ,
int * bits )
{
if ( UR ( port , UART_R_CON ) & UARTEN ) {
unsigned int fcon = UR ( port , UART_R_FCON ) ;
unsigned int quot = UR ( port , UART_R_BRCON ) + 1 ;
switch ( fcon & ( PEN | EPS ) ) {
default : * parity = ' n ' ; break ;
case PEN : * parity = ' o ' ; break ;
case PEN | EPS : * parity = ' e ' ; break ;
}
switch ( fcon & WLEN ) {
default :
case WLEN_8 : * bits = 8 ; break ;
case WLEN_7 : * bits = 7 ; break ;
case WLEN_6 : * bits = 6 ; break ;
case WLEN_5 : * bits = 5 ; break ;
}
* baud = port - > uartclk / ( 16 * quot ) ;
}
}
static int __init lh7a40xuart_console_setup ( struct console * co , char * options )
{
struct uart_port * port ;
int baud = 38400 ;
int bits = 8 ;
int parity = ' n ' ;
int flow = ' n ' ;
if ( co - > index > = DEV_NR ) /* Bounds check on device number */
co - > index = 0 ;
port = & lh7a40x_ports [ co - > index ] . port ;
if ( options )
uart_parse_options ( options , & baud , & parity , & bits , & flow ) ;
else
lh7a40xuart_console_get_options ( port , & baud , & parity , & bits ) ;
return uart_set_options ( port , co , baud , parity , bits , flow ) ;
}
2005-09-15 01:36:03 +04:00
static struct uart_driver lh7a40x_reg ;
2005-04-17 02:20:36 +04:00
static struct console lh7a40x_console = {
. name = " ttyAM " ,
. write = lh7a40xuart_console_write ,
. device = uart_console_device ,
. setup = lh7a40xuart_console_setup ,
. flags = CON_PRINTBUFFER ,
. index = - 1 ,
. data = & lh7a40x_reg ,
} ;
static int __init lh7a40xuart_console_init ( void )
{
register_console ( & lh7a40x_console ) ;
return 0 ;
}
console_initcall ( lh7a40xuart_console_init ) ;
# endif
static struct uart_driver lh7a40x_reg = {
. owner = THIS_MODULE ,
. driver_name = " ttyAM " ,
. dev_name = " ttyAM " ,
. major = DEV_MAJOR ,
. minor = DEV_MINOR ,
. nr = DEV_NR ,
. cons = LH7A40X_CONSOLE ,
} ;
static int __init lh7a40xuart_init ( void )
{
int ret ;
printk ( KERN_INFO " serial: LH7A40X serial driver \n " ) ;
ret = uart_register_driver ( & lh7a40x_reg ) ;
if ( ret = = 0 ) {
int i ;
for ( i = 0 ; i < DEV_NR ; i + + )
uart_add_one_port ( & lh7a40x_reg ,
& lh7a40x_ports [ i ] . port ) ;
}
return ret ;
}
static void __exit lh7a40xuart_exit ( void )
{
int i ;
for ( i = 0 ; i < DEV_NR ; i + + )
uart_remove_one_port ( & lh7a40x_reg , & lh7a40x_ports [ i ] . port ) ;
uart_unregister_driver ( & lh7a40x_reg ) ;
}
module_init ( lh7a40xuart_init ) ;
module_exit ( lh7a40xuart_exit ) ;
MODULE_AUTHOR ( " Marc Singer " ) ;
MODULE_DESCRIPTION ( " Sharp LH7A40X serial port driver " ) ;
MODULE_LICENSE ( " GPL " ) ;