/*
* Driver core for Samsung SoC onboard UARTs.
*
* Ben Dooks, Copyright (c) 2003-2008 Simtec Electronics
* http://armlinux.simtec.co.uk/
*
* 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.
*/
/* Hote on 2410 error handling
*
* The s3c2410 manual has a love/hate affair with the contents of the
* UERSTAT register in the UART blocks, and keeps marking some of the
* error bits as reserved. Having checked with the s3c2410x01,
* it copes with BREAKs properly, so I am happy to ignore the RESERVED
* feature from the latter versions of the manual.
*
* If it becomes aparrent that latter versions of the 2410 remove these
* bits, then action will have to be taken to differentiate the versions
* and change the policy on BREAK
*
* BJD, 04-Nov-2004
*/
#if defined(CONFIG_SERIAL_SAMSUNG_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)
#define SUPPORT_SYSRQ
#endif
#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/init.h>
#include <linux/sysrq.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/serial_s3c.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/cpufreq.h>
#include <linux/of.h>
#include <asm/irq.h>
#include "samsung.h"
#if defined(CONFIG_SERIAL_SAMSUNG_DEBUG) && \
defined(CONFIG_DEBUG_LL) && \
!defined(MODULE)
extern void printascii(const char *);
__printf(1, 2)
static void dbg(const char *fmt, ...)
{
va_list va;
char buff[256];
va_start(va, fmt);
vscnprintf(buff, sizeof(buff), fmt, va);
va_end(va);
printascii(buff);
}
#else
#define dbg(fmt, ...) do { if (0) no_printk(fmt, ##__VA_ARGS__); } while (0)
#endif
/* UART name and device definitions */
#define S3C24XX_SERIAL_NAME "ttySAC"
#define S3C24XX_SERIAL_MAJOR 204
#define S3C24XX_SERIAL_MINOR 64
/* macros to change one thing to another */
#define tx_enabled(port) ((port)->unused[0])
#define rx_enabled(port) ((port)->unused[1])
/* flag to ignore all characters coming in */
#define RXSTAT_DUMMY_READ (0x10000000)
static inline struct s3c24xx_uart_port *to_ourport(struct uart_port *port)
{
return container_of(port, struct s3c24xx_uart_port, port);
}
/* translate a port to the device name */
static inline const char *s3c24xx_serial_portname(struct uart_port *port)
{
return to_platform_device(port->dev)->name;
}
static int s3c24xx_serial_txempty_nofifo(struct uart_port *port)
{
return rd_regl(port, S3C2410_UTRSTAT) & S3C2410_UTRSTAT_TXE;
}
/*
* s3c64xx and later SoC's include the interrupt mask and status registers in
* the controller itself, unlike the s3c24xx SoC's which have these registers
* in the interrupt controller. Check if the port type is s3c64xx or higher.
*/
static int s3c24xx_serial_has_interrupt_mask(struct uart_port *port)
{
return to_ourport(port)->info->type == PORT_S3C6400;
}
static void s3c24xx_serial_rx_enable(struct uart_port *port)
{
unsigned long flags;
unsigned int ucon, ufcon;
int count = 10000;
spin_lock_irqsave(&port->lock, flags);
while (--count && !s3c24xx_serial_txempty_nofifo(port))
udelay(100);
ufcon = rd_regl(port, S3C2410_UFCON);
ufcon |= S3C2410_UFCON_RESETRX;
wr_regl(port, S3C2410_UFCON, ufcon);
ucon = rd_regl(port, S3C2410_UCON);
ucon |= S3C2410_UCON_RXIRQMODE;
wr_regl(port, S3C2410_UCON, ucon);
rx_enabled(port) = 1;
spin_unlock_irqrestore(&port->lock, flags);
}
static void s3c24xx_serial_rx_disable(struct uart_port *port)
{
unsigned long flags;
unsigned int ucon;
spin_lock_irqsave(&port->lock, flags);
ucon = rd_regl(port, S3C2410_UCON);
ucon &= ~S3C2410_UCON_RXIRQMODE;
wr_regl(port, S3C2410_UCON, ucon);
rx_enabled(port) = 0;
spin_unlock_irqrestore(&port->lock, flags);
}
static void s3c24xx_serial_stop_tx(struct uart_port *port)
{
struct s3c24xx_uart_port *ourport = to_ourport(port);
if (tx_enabled(port)) {
if (s3c24xx_serial_has_interrupt_mask(port))
__set_bit(S3C64XX_UINTM_TXD,
portaddrl(port, S3C64XX_UINTM));
else
disable_irq_nosync(ourport->tx_irq);
tx_enabled(port) = 0;
if (port->flags & UPF_CONS_FLOW)
s3c24xx_serial_rx_enable(port);
}
}
static void s3c24xx_serial_start_tx(struct uart_port *port)
{
struct s3c24xx_uart_port *ourport = to_ourport(port);
if (!tx_enabled(port)) {
if (port->flags & UPF_CONS_FLOW)
s3c24xx_serial_rx_disable(port);
if (s3c24xx_serial_has_interrupt_mask(port))
__clear_bit(S3C64XX_UINTM_TXD,
portaddrl(port, S3C64XX_UINTM));
else
enable_irq(ourport->tx_irq);
tx_enabled(port) = 1;
}
}
static void s3c24xx_serial_stop_rx(struct uart_port *port)
{
struct s3c24xx_uart_port *ourport = to_ourport(port);
if (rx_enabled(port)) {
dbg("s3c24xx_serial_stop_rx: port=%p\n", port);
if (s3c24xx_serial_has_interrupt_mask(port))
__set_bit(S3C64XX_UINTM_RXD,
portaddrl(port, S3C64XX_UINTM));
else
disable_irq_nosync(ourport->rx_irq);
rx_enabled(port) = 0;
}
}
static inline struct s3c24xx_uart_info
*s3c24xx_port_to_info(struct uart_port *port)
{
return to_ourport(port)->info;
}
static inline struct s3c2410_uartcfg
*s3c24xx_port_to_cfg(struct uart_port *port)
{
struct s3c24xx_uart_port *ourport;
if (port->dev == NULL)
return NULL;
ourport = container_of(port, struct s3c24xx_uart_port, port);
return ourport->cfg;
}
static int s3c24xx_serial_rx_fifocnt(struct s3c24xx_uart_port *ourport,
unsigned long ufstat)
{
struct s3c24xx_uart_info *info = ourport->info;
if (ufstat & info->rx_fifofull)
return ourport->port.fifosize;
return (ufstat & info->rx_fifomask) >> info->rx_fifoshift;
}
/* ? - where has parity gone?? */
#define S3C2410_UERSTAT_PARITY (0x1000)
static irqreturn_t
s3c24xx_serial_rx_chars(int irq, void *dev_id)
{
struct s3c24xx_uart_port *ourport = dev_id;
struct uart_port *port = &ourport->port;
unsigned int ufcon, ch, flag, ufstat, uerstat;
unsigned long flags;
int max_count = port->fifosize;
spin_lock_irqsave(&port->lock, flags);
while (max_count-- > 0) {
ufcon = rd_regl(port, S3C2410_UFCON);
ufstat = rd_regl(port, S3C2410_UFSTAT);
if (s3c24xx_serial_rx_fifocnt(ourport, ufstat) == 0)
break;
uerstat = rd_regl(port, S3C2410_UERSTAT);
ch = rd_regb(port, S3C2410_URXH);
if (port->flags & UPF_CONS_FLOW) {
int txe = s3c24xx_serial_txempty_nofifo(port);
if (rx_enabled(port)) {
if (!txe) {
rx_enabled(port) = 0;
continue;
}
} else {
if (txe) {
ufcon |= S3C2410_UFCON_RESETRX;
wr_regl(port, S3C2410_UFCON, ufcon);
rx_enabled(port) = 1;
spin_unlock_irqrestore(&port->lock,
flags);
goto out;
}
continue;
}
}
/* insert the character into the buffer */
flag = TTY_NORMAL;
port->icount.rx++;
if (unlikely(uerstat & S3C2410_UERSTAT_ANY)) {
dbg("rxerr: port ch=0x%02x, rxs=0x%08x\n",
ch, uerstat);
/* check for break */
if (uerstat & S3C2410_UERSTAT_BREAK) {
dbg("break!\n");
port->icount.brk++;
if (uart_handle_break(port))
goto ignore_char;
}
if (uerstat & S3C2410_UERSTAT_FRAME)
port->icount.frame++;
if (uerstat & S3C2410_UERSTAT_OVERRUN)
port->icount.overrun++;
uerstat &= port->read_status_mask;
if (uerstat & S3C2410_UERSTAT_BREAK)
flag = TTY_BREAK;
else if (uerstat & S3C2410_UERSTAT_PARITY)
flag = TTY_PARITY;
else if (uerstat & (S3C2410_UERSTAT_FRAME |
S3C2410_UERSTAT_OVERRUN))
flag = TTY_FRAME;
}
if (uart_handle_sysrq_char(port, ch))
goto ignore_char;
uart_insert_char(port, uerstat, S3C2410_UERSTAT_OVERRUN,
ch, flag);
ignore_char:
continue;
}
spin_unlock_irqrestore(&port->lock, flags);
tty_flip_buffer_push(&port->state->port);
out:
return IRQ_HANDLED;
}
static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id)
{
struct s3c24xx_uart_port *ourport = id;