/*
* mfd.c: driver for High Speed UART device of Intel Medfield platform
*
* Refer pxa.c, 8250.c and some other drivers in drivers/serial/
*
* (C) Copyright 2010 Intel Corporation
*
* 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; version 2
* of the License.
*/
/* Notes:
* 1. DMA channel allocation: 0/1 channel are assigned to port 0,
* 2/3 chan to port 1, 4/5 chan to port 3. Even number chans
* are used for RX, odd chans for TX
*
* 2. The RI/DSR/DCD/DTR are not pinned out, DCD & DSR are always
* asserted, only when the HW is reset the DDCD and DDSR will
* be triggered
*/
#if defined(CONFIG_SERIAL_MFD_HSU_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)
#define SUPPORT_SYSRQ
#endif
#include <linux/module.h>
#include <linux/init.h>
#include <linux/console.h>
#include <linux/sysrq.h>
#include <linux/slab.h>
#include <linux/serial_reg.h>
#include <linux/circ_buf.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial_core.h>
#include <linux/serial_mfd.h>
#include <linux/dma-mapping.h>
#include <linux/pci.h>
#include <linux/nmi.h>
#include <linux/io.h>
#include <linux/debugfs.h>
#include <linux/pm_runtime.h>
#define HSU_DMA_BUF_SIZE 2048
#define chan_readl(chan, offset) readl(chan->reg + offset)
#define chan_writel(chan, offset, val) writel(val, chan->reg + offset)
#define mfd_readl(obj, offset) readl(obj->reg + offset)
#define mfd_writel(obj, offset, val) writel(val, obj->reg + offset)
static int hsu_dma_enable;
module_param(hsu_dma_enable, int, 0);
MODULE_PARM_DESC(hsu_dma_enable,
"It is a bitmap to set working mode, if bit[x] is 1, then port[x] will work in DMA mode, otherwise in PIO mode.");
struct hsu_dma_buffer {
u8 *buf;
dma_addr_t dma_addr;
u32 dma_size;
u32 ofs;
};
struct hsu_dma_chan {
u32 id;
enum dma_data_direction dirt;
struct uart_hsu_port *uport;
void __iomem *reg;
};
struct uart_hsu_port {
struct uart_port port;
unsigned char ier;
unsigned char lcr;
unsigned char mcr;
unsigned int lsr_break_flag;
char name[12];
int index;
struct device *dev;
struct hsu_dma_chan *txc;
struct hsu_dma_chan *rxc;
struct hsu_dma_buffer txbuf;
struct hsu_dma_buffer rxbuf;
int use_dma; /* flag for DMA/PIO */
int running;
int dma_tx_on;
};
/* Top level data structure of HSU */
struct hsu_port {
void __iomem *reg;
unsigned long paddr;
unsigned long iolen;
u32 irq;
struct uart_hsu_port port[3];
struct hsu_dma_chan chans[10];
struct dentry *debugfs;
};
static inline unsigned int serial_in(struct uart_hsu_port *up, int offset)
{
unsigned int val;
if (offset > UART_MSR) {
offset <<= 2;
val = readl(up->port.membase + offset);
} else
val = (unsigned int)readb(up->port.membase + offset);
return val;
}
static inline void serial_out(struct uart_hsu_port *up, int offset, int value)
{
if (offset > UART_MSR) {
offset <<= 2;
writel(value, up->port.membase + offset);
} else {
unsigned char val = value & 0xff;
writeb(val, up->port.membase + offset);
}
}
#ifdef CONFIG_DEBUG_FS
#define HSU_REGS_BUFSIZE 1024
static ssize_t port_show_regs(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
struct uart_hsu_port *up = file->private_data;
char *buf;
u32 len = 0;
ssize_t ret;
buf = kzalloc(HSU_REGS_BUFSIZE, GFP_KERNEL);
if (!buf)
return 0;
len += snprintf(buf + len, HSU_REGS_BUFSIZE - len,
"MFD HSU port[%d] regs:\n", up->index);
len += snprintf(buf + len, HSU_REGS_BUFSIZE - len,
"=================================\n");
len += snprintf(buf + len, HSU_REGS_BUFSIZE - len,
"IER: \t\t0x%08x\n", serial_in(up, UART_IER));
len += snprintf(buf + len, HSU_REGS_BUFSIZE - len,
"IIR: \t\t0x%08x\n", serial_in(up, UART_IIR));
len += snprintf(buf + len, HSU_REGS_BUFSIZE - len,
"LCR: \t\t0x%08x\n", serial_in(up, UART_LCR));
len += snprintf(buf + len, HSU_REGS_BUFSIZE - len,
"MCR: \t\t0x%08x\n", serial_in(up, UART_MCR));
len += snprintf(buf + len, HSU_REGS_BUFSIZE - len,
"LSR: \t\t0x%08x\n", serial_in(up, UART_LSR));
len += snprintf(buf + len, HSU_REGS_BUFSIZE - len,
"MSR: \t\t0x%08x\n", serial_in(up, UART_MSR));
len += snprintf(buf + len, HSU_REGS_BUFSIZE - len,
"FOR: \t\t0x%08x\n", serial_in(up, UART_FOR));
len += snprintf(buf + len, HSU_REGS_BUFSIZE - len,
"PS: \t\t0x%08x\n", serial_in(up, UART_PS));
len += snprintf(buf + len, HSU_REGS_BUFSIZE - len,
"MUL: \t\t0x%08x\n", serial_in(up, UART_MUL));
len += snprintf(buf + len, HSU_REGS_BUFSIZE - len,
"DIV: \t\t0x%08x\n", serial_in(up, UART_DIV));
if (len > HSU_REGS_BUFSIZE)
len = HSU_REGS_BUFSIZE;
ret = simple_read_from_buffer(user_buf, count, ppos, buf, len);
kfree(buf);
return ret;
}
static ssize_t dma_show_regs(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
struct hsu_dma_chan *chan = file->private_data;
char *buf;
u32 len = 0;
ssize_t ret;
buf = kzalloc(HSU_REGS_BUFSIZE, GFP_KERNEL);
if (!buf)
return 0;
len += snprintf(buf + len, HSU_REGS_BUFSIZE - len,
"MFD HSU DMA channel [%d] regs:\n", chan->id);
len += snprintf(buf + len, HSU_REGS_BUFSIZE - len,
"=================================\n");
len += snprintf(buf + len, HSU_REGS_BUFSIZE - len,
"CR: \t\t0x%08x\n", chan_readl(chan, HSU_CH_CR));
len += snprintf(buf + len, HSU_REGS_BUFSIZE - len,
"DCR: \t\t0x%08x\n", chan_readl(chan, HSU_CH_DCR));
len += snprintf(buf + len, HSU_REGS_BUFSIZE - len,
"BSR: \t\t0x%08x\n", chan_readl(chan, HSU_CH_BSR));
len += snprintf(buf + len, HSU_REGS_BUFSIZE - len,
"MOTSR: \t\t0x%08x\n", chan_readl(chan, HSU_CH_MOTSR));
len += snprintf(buf + len, HSU_REGS_BUFSIZE - len,
"D0SAR: \t\t0x%08x\n", chan_readl(chan, HSU_CH_D0SAR));
len += snprintf(buf + len, HSU_REGS_BUFSIZE - len,
"D0TSR: \t\t0x%08x\n", chan_readl(chan, HSU_CH_D0TSR));
len += snprintf(buf + len, HSU_REGS_BUFSIZE - len,
"D0SAR: \t\t0x%08x\n", chan_readl(chan, HSU_CH_D1SAR));
len += snprintf(buf + len, HSU_REGS_BUFSIZE - len,
"D0TSR: \t\t0x%08x\n", chan_readl(chan, HSU_CH_D1TSR));
len += snprintf(buf + len, HSU_REGS_BUFSIZE - len,
"D0SAR: \t\t0x%08x\n", chan_readl(chan, HSU_CH_D2SAR));
len += snprintf(buf + len, HSU_REGS_BUFSIZE - len,
"D0TSR: \t\t0x%08x\n", chan_readl(chan, HSU_CH_D2TSR));
len += snprintf(buf + len, HSU_REGS_BUFSIZE - len,
"D0SAR: \t\t0x%08x\n", chan_readl(chan, HSU_CH_D3SAR));
len += snprintf(buf + len, HSU_REGS_BUFSIZE - len,
"D0TSR: \t\t0x%08x\n", chan_readl(chan, HSU_CH_D3TSR));
if (len > HSU_REGS_BUFSIZE)
len = HSU_REGS_BUFSIZE;
ret = simple_read_from_buffer(user_buf, count, ppos, buf, len);
kfree(buf);
return ret;
}
static const struct file_operations port_regs_ops = {
.owner = THIS_MODULE,
.open = simple_open,
.read = port_show_regs,
.llseek = default_llseek,
};
static const struct file_operations dma_regs_ops = {
.owner = THIS_MODULE,
.open = simple_open,
.read = dma_show_regs,
.llseek = default_llseek,
};
static int hsu_debugfs_init(struct hsu_port *hsu)
{
int i;
char name[32];
hsu->debugfs = debugfs_create_dir("hsu", NULL);
if (!hsu->debugfs)
return -ENOMEM;
for (i = 0; i < 3; i++) {
snprintf(name, sizeof(name), "port_%d_regs", i);
debugfs_create_file(name, S_IFREG | S_IRUGO,
hsu->debugfs, (void *)(&hsu->port[i]), &port_regs_ops);
}
for (i = 0; i < 6; i++) {
snprintf(name, sizeof(name), "dma_chan_%d_regs", i);
debugfs_create_file(name, S_IFREG | S_IRUGO,
hsu->debugfs, (void *)&hsu->chans[i], &dma_regs_ops);
}
return 0;
}
static void hsu_debugfs_remove(struct hsu_port *hsu)
{
if (hsu->debugfs)
debugfs_remove_recursive(hsu->debugfs);
}
#else
static inline int hsu_debugfs_init(struct hsu_port *hsu)
{
return 0;
}
static inline void hsu_debugfs_remove(struct hsu_port *hsu)
{
}
#endif /* CONFIG_DEBUG_FS */
static void serial_hsu_enable_ms(struct uart_port *port)
{
struct uart_hsu_port *
|