#include
#include
#include
#include
#include
#include
#include
#include
#define FIFO_SIZE PAGE_SIZE
__u32 cts;
__u32 rng;
__u32 rx;
__u32 frame;
__u32 parity;
};
struct sdio_uart_port {
struct kref kref;
unsigned int index;
struct mutex func_lock;
unsigned int regs_offset;
spinlock_t write_lock;
unsigned int uartclk;
unsigned int rx_mctrl;
unsigned int ignore_status_mask;
unsigned char ier;
};
static struct sdio_uart_port *sdio_uart_table[UART_NR];
{
mutex_init(&port->func_lock);
if (kfifo_alloc(&port->xmit_fifo, FIFO_SIZE, GFP_KERNEL))
for (index = 0; index < UART_NR; index++) {
port->index = index;
ret = 0;
}
spin_unlock(&sdio_uart_table_lock);
return ret;
{
return NULL;
spin_lock(&sdio_uart_table_lock);
if (port)
spin_unlock(&sdio_uart_table_lock);
return port;
{
container_of(kref, struct sdio_uart_port, kref);
kfree(port);
{
}
static void sdio_uart_port_remove(struct sdio_uart_port *port)
struct sdio_func *func;
sdio_uart_table[port->index] = NULL;
* We're killing a port that potentially still is in use by
* to the SDIO function and arrange for the tty layer to
* Beware: the lock ordering is critical.
mutex_lock(&port->port.mutex);
func = port->func;
port->func = NULL;
tty = tty_port_tty_get(&port->port);
if (tty) {
tty_kref_put(tty);
mutex_unlock(&port->port.mutex);
sdio_disable_func(func);
}
static int sdio_uart_claim_func(struct sdio_uart_port *port)
mutex_lock(&port->func_lock);
mutex_unlock(&port->func_lock);
}
sdio_claim_host(port->func);
return 0;
{
sdio_release_host(port->func);
{
c = sdio_readb(port->func, port->regs_offset + offset, NULL);
}
static inline void sdio_out(struct sdio_uart_port *port, int offset, int value)
sdio_writeb(port->func, value, port->regs_offset + offset, NULL);
{
unsigned int ret;
/* FIXME: What stops this losing the delta bits and breaking
status = sdio_in(port, UART_MSR);
ret = 0;
ret |= TIOCM_CAR;
ret |= TIOCM_RNG;
ret |= TIOCM_DSR;
ret |= TIOCM_CTS;
}
static void sdio_uart_write_mctrl(struct sdio_uart_port *port,
{
mcr |= UART_MCR_RTS;
mcr |= UART_MCR_DTR;
mcr |= UART_MCR_OUT1;
mcr |= UART_MCR_OUT2;
mcr |= UART_MCR_LOOP;
sdio_out(port, UART_MCR, mcr);
unsigned int set, unsigned int clear)
unsigned int old;
old = port->mctrl;
if (old != port->mctrl)
}
#define sdio_uart_set_mctrl(port, x) sdio_uart_update_mctrl(port, x, 0)
struct ktermios *termios,
{
unsigned int baud, quot;
switch (termios->c_cflag & CSIZE) {
cval = UART_LCR_WLEN5;
case CS6:
break;
cval = UART_LCR_WLEN7;
default:
cval = UART_LCR_WLEN8;
}
if (termios->c_cflag & CSTOPB)
if (termios->c_cflag & PARENB)
if (!(termios->c_cflag & PARODD))
baud = tty_termios_baud_rate(termios);
baud = 9600; /* Special case: B0 rate. */
break;
* Oops, the quotient was zero. Try again with the old
*/
if (old) {
old = NULL;
termios->c_cflag |= B9600;
quot = (2 * port->uartclk + baud) / (2 * baud);
if (baud < 2400)
else
if (termios->c_iflag & INPCK)
if (termios->c_iflag & (BRKINT | PARMRK))
* Characters to ignore
port->ignore_status_mask = 0;
port->ignore_status_mask |= UART_LSR_PE | UART_LSR_FE;
port->ignore_status_mask |= UART_LSR_BI;
* If we're ignoring parity and break indicators,
*/
port->ignore_status_mask |= UART_LSR_OE;
* ignore all characters if CREAD is not set
if ((termios->c_cflag & CREAD) == 0)
* CTS flow control flag and modem status interrupts
port->ier &= ~UART_IER_MSI;
port->ier |= UART_IER_MSI;
port->lcr = cval;
sdio_out(port, UART_IER, port->ier);
sdio_out(port, UART_DLL, quot & 0xff);
sdio_out(port, UART_LCR, cval);
}
static void sdio_uart_start_tx(struct sdio_uart_port *port)
if (!(port->ier & UART_IER_THRI)) {
sdio_out(port, UART_IER, port->ier);
}
static void sdio_uart_stop_tx(struct sdio_uart_port *port)
if (port->ier & UART_IER_THRI) {
sdio_out(port, UART_IER, port->ier);
}
static void sdio_uart_stop_rx(struct sdio_uart_port *port)
port->ier &= ~UART_IER_RLSI;
sdio_out(port, UART_IER, port->ier);
unsigned int *status)
struct tty_struct *tty = tty_port_tty_get(&port->port);
int max_count = 256;
do {
flag = TTY_NORMAL;
UART_LSR_FE | UART_LSR_OE))) {
* For statistics only
if (*status & UART_LSR_BI) {
port->icount.brk++;
port->icount.parity++;
port->icount.frame++;
port->icount.overrun++;
/*
*/
if (*status & UART_LSR_BI)
else if (*status & UART_LSR_PE)
else if (*status & UART_LSR_FE)
}
if ((*status & port->ignore_status_mask & ~UART_LSR_OE) == 0)
tty_insert_flip_char(tty, ch, flag);
/*
* it doesn't affect the current character.
if (*status & ~port->ignore_status_mask & UART_LSR_OE)
tty_insert_flip_char(tty, 0, TTY_OVERRUN);
*status = sdio_in(port, UART_LSR);
if (tty) {
tty_kref_put(tty);
}
static void sdio_uart_transmit_chars(struct sdio_uart_port *port)
struct kfifo *xmit = &port->xmit_fifo;
struct tty_struct *tty;
int len;
if (port->x_char) {
port->icount.tx++;
return;
tty->stopped || tty->hw_stopped) {
tty_kref_put(tty);
}
len = kfifo_out_locked(xmit, iobuf, 16, &port->write_lock);
sdio_out(port, UART_TX, iobuf[count]);
}
len = kfifo_len(xmit);
tty_wakeup(tty);
sdio_uart_stop_tx(port);
tty_kref_put(tty);
{
struct tty_struct *tty;
status = sdio_in(port, UART_MSR);
if ((status & UART_MSR_ANY_DELTA) == 0)
port->icount.rng++;
port->icount.dsr++;
port->icount.dcd++;
if (status & UART_MSR_DCD)
else {
tty = tty_port_tty_get(&port->port);
tty_hangup(tty);
}
}
port->icount.cts++;
if (tty && (tty->termios->c_cflag & CRTSCTS)) {
if (tty->hw_stopped) {
tty->hw_stopped = 0;
tty_wakeup(tty);
} else {
tty->hw_stopped = 1;
}
}
}
* This handles the interrupt from one port.
static void sdio_uart_irq(struct sdio_func *func)
struct sdio_uart_port *port = sdio_get_drvdata(func);
* In a few places sdio_uart_irq() is called directly instead of
* thread scheduled in order to reduce latency. However, some
* (serial echo, flow control, etc.) through those same places
*/
return;
iir = sdio_in(port, UART_IIR);
return;
port->in_sdio_uart_irq = current;
if (lsr & UART_LSR_DR)
sdio_uart_check_modem_status(port);
sdio_uart_transmit_chars(port);
}
static int uart_carrier_raised(struct tty_port *tport)
struct sdio_uart_port *port =
unsigned int ret = sdio_uart_claim_func(port);
return 1;
sdio_uart_release_func(port);
return 1;
}
/**
* @tport: tty port to be updated
*
* adjusted during an open, close and hangup.
{
container_of(tport, struct sdio_uart_port, port);
if (ret)
if (onoff == 0)
else
sdio_uart_release_func(port);
* sdio_uart_activate - start up hardware
* @tty: tty bound to this port
* Activate a tty port. The port locking guarantees us this will be
* shutdown method run exactly once to match. Start up and shutdown are
* at the same time even during a hangup event.
* If we successfully start up the port we take an extra kref as we
*/
static int sdio_uart_activate(struct tty_port *tport, struct tty_struct *tty)
struct sdio_uart_port *port =
int ret;
/*
* once we have successfully opened the port.
set_bit(TTY_IO_ERROR, &tty->flags);
kfifo_reset(&port->xmit_fifo);
ret = sdio_uart_claim_func(port);
return ret;
if (ret)
ret = sdio_claim_irq(port->func, sdio_uart_irq);
goto err2;
/*
* (they will be reenabled in sdio_change_speed())
sdio_out(port, UART_FCR, UART_FCR_ENABLE_FIFO);
UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT);
* Clear the interrupt registers.
(void) sdio_in(port, UART_LSR);
(void) sdio_in(port, UART_IIR);
* Now, initialize the UART
sdio_out(port, UART_LCR, UART_LCR_WLEN8);
port->ier = UART_IER_RLSI|UART_IER_RDI|UART_IER_RTOIE|UART_IER_UUE;
sdio_uart_set_mctrl(port, TIOCM_RTS | TIOCM_DTR);
if (tty->termios->c_cflag & CRTSCTS)
tty->hw_stopped = 1;
clear_bit(TTY_IO_ERROR, &tty->flags);
/* Kick the IRQ handler once while we're still holding the host lock */
return 0;
err2:
err1:
return ret;
* sdio_uart_shutdown - stop hardware
*
* run only if a successful matching activate already ran. The two are
* at the same time even during a hangup event.
{
container_of(tport, struct sdio_uart_port, port);
if (ret)
sdio_release_irq(port->func);
sdio_out(port, UART_IER, 0);
sdio_uart_clear_mctrl(port, TIOCM_OUT2);
/* Disable break condition and FIFOs. */
sdio_out(port, UART_LCR, port->lcr);
UART_FCR_CLEAR_RCVR |
sdio_out(port, UART_FCR, 0);
sdio_disable_func(port->func);
sdio_uart_release_func(port);
* sdio_uart_install - install method
* @tty: the tty being bound
* Look up and bind the tty and the driver together. Initialize
*/
static int sdio_uart_install(struct tty_driver *driver, struct tty_struct *tty)
int idx = tty->index;
int ret = tty_standard_install(driver, tty);
if (ret == 0)
tty->driver_data = port;
sdio_uart_port_put(port);
}
/**
* @tty: the tty being destroyed
* Called asynchronously when the last reference to the tty is dropped.
*/
static void sdio_uart_cleanup(struct tty_struct *tty)
struct sdio_uart_port *port = tty->driver_data;
sdio_uart_port_put(port);
* Open/close/hangup is now entirely boilerplate
{
return tty_port_open(&port->port, tty, filp);
{
tty_port_close(&port->port, tty, filp);
{
tty_port_hangup(&port->port);
int count)
struct sdio_uart_port *port = tty->driver_data;
return -ENODEV;
ret = kfifo_in_locked(&port->xmit_fifo, buf, count, &port->write_lock);
int err = sdio_uart_claim_func(port);
sdio_uart_start_tx(port);
sdio_uart_release_func(port);
ret = err;
}
static int sdio_uart_write_room(struct tty_struct *tty)
struct sdio_uart_port *port = tty->driver_data;
}
static int sdio_uart_chars_in_buffer(struct tty_struct *tty)
struct sdio_uart_port *port = tty->driver_data;
}
static void sdio_uart_send_xchar(struct tty_struct *tty, char ch)
struct sdio_uart_port *port = tty->driver_data;
port->x_char = ch;
if (sdio_uart_claim_func(port) != 0)
sdio_uart_start_tx(port);
sdio_uart_release_func(port);
}
static void sdio_uart_throttle(struct tty_struct *tty)
struct sdio_uart_port *port = tty->driver_data;
if (!I_IXOFF(tty) && !(tty->termios->c_cflag & CRTSCTS))
return;
if (I_IXOFF(tty)) {
sdio_uart_start_tx(port);
sdio_uart_clear_mctrl(port, TIOCM_RTS);
sdio_uart_irq(port->func);
}
static void sdio_uart_unthrottle(struct tty_struct *tty)
struct sdio_uart_port *port = tty->driver_data;
if (!I_IXOFF(tty) && !(tty->termios->c_cflag & CRTSCTS))
return;
if (I_IXOFF(tty)) {
port->x_char = 0;
port->x_char = START_CHAR(tty);
}
sdio_uart_set_mctrl(port, TIOCM_RTS);
sdio_uart_irq(port->func);
}
static void sdio_uart_set_termios(struct tty_struct *tty,
{
unsigned int cflag = tty->termios->c_cflag;
if (sdio_uart_claim_func(port) != 0)
if ((old_termios->c_cflag & CBAUD) && !(cflag & CBAUD))
if (!(old_termios->c_cflag & CBAUD) && (cflag & CBAUD)) {
if (!(cflag & CRTSCTS) || !test_bit(TTY_THROTTLED, &tty->flags))
sdio_uart_set_mctrl(port, mask);
if ((old_termios->c_cflag & CRTSCTS) && !(cflag & CRTSCTS)) {
sdio_uart_start_tx(port);
if (!(old_termios->c_cflag & CRTSCTS) && (cflag & CRTSCTS)) {
tty->hw_stopped = 1;
}
}
static int sdio_uart_break_ctl(struct tty_struct *tty, int break_state)
struct sdio_uart_port *port = tty->driver_data;
if (result != 0)
port->lcr |= UART_LCR_SBC;
port->lcr &= ~UART_LCR_SBC;
return 0;
{
int result;
result = sdio_uart_claim_func(port);
result = port->mctrl | sdio_uart_get_mctrl(port);
}
return result;
unsigned int set, unsigned int clear)
struct sdio_uart_port *port = tty->driver_data;
if (!result) {
sdio_uart_release_func(port);
}
static int sdio_uart_proc_show(struct seq_file *m, void *v)
int i;
seq_printf(m, "serinfo:1.0 driver%s%s revision:%sn",
for (i = 0; i < UART_NR; i++) {
if (port) {
if (capable(CAP_SYS_ADMIN)) {
port->icount.tx, port->icount.rx);
seq_printf(m, " fe:%d",
if (port->icount.parity)
port->icount.parity);
seq_printf(m, " brk:%d",
if (port->icount.overrun)
port->icount.overrun);
seq_printf(m, " cts:%d",
if (port->icount.dsr)
port->icount.dsr);
seq_printf(m, " rng:%d",
if (port->icount.dcd)
port->icount.dcd);
sdio_uart_port_put(port);
}
return 0;
{
}
static const struct file_operations sdio_uart_proc_fops = {
.open = sdio_uart_proc_open,
.llseek = seq_lseek,
};
static const struct tty_port_operations sdio_uart_port_ops = {
.carrier_raised = uart_carrier_raised,
.activate = sdio_uart_activate,
.open = sdio_uart_open,
.write = sdio_uart_write,
.chars_in_buffer = sdio_uart_chars_in_buffer,
.throttle = sdio_uart_throttle,
.set_termios = sdio_uart_set_termios,
.break_ctl = sdio_uart_break_ctl,
.tiocmset = sdio_uart_tiocmset,
.cleanup = sdio_uart_cleanup,
};
static struct tty_driver *sdio_uart_tty_driver;
static int sdio_uart_probe(struct sdio_func *func,
{
int ret;
port = kzalloc(sizeof(struct sdio_uart_port), GFP_KERNEL);
return -ENOMEM;
if (func->class == SDIO_CLASS_UART) {
sdio_func_id(func));
return -ENOSYS;
/*
* and SUBTPL_RCVCAPS.
for (tpl = func->tuples; tpl; tpl = tpl->next) {
continue;
continue;
break;
if (!tpl) {
"%s: can't find tuple 0x91 subtuple 0 (SUBTPL_SIOREG) for GPS classn",
kfree(port);
}
sdio_func_id(func), tpl->data[2], tpl->data[3]);
(tpl->data[5] << 8) |
pr_debug("%s: regs offset = 0x%xn",
port->uartclk = tpl->data[7] * 115200;
port->uartclk = 115200;
sdio_func_id(func), port->uartclk,
} else {
return -EINVAL;
sdio_set_drvdata(func, port);
port->port.ops = &sdio_uart_port_ops;
ret = sdio_uart_add_port(port);
kfree(port);
struct device *dev;
port->index, &func->dev);
sdio_uart_port_remove(port);
}
}
static void sdio_uart_remove(struct sdio_func *func)
struct sdio_uart_port *port = sdio_get_drvdata(func);
tty_unregister_device(sdio_uart_tty_driver, port->index);
}
static const struct sdio_device_id sdio_uart_ids[] = {
{ SDIO_DEVICE_CLASS(SDIO_CLASS_GPS) },
};
MODULE_DEVICE_TABLE(sdio, sdio_uart_ids);
static struct sdio_driver sdio_uart_driver = {
.remove = sdio_uart_remove,
.id_table = sdio_uart_ids,
{
struct tty_driver *tty_drv;
sdio_uart_tty_driver = tty_drv = alloc_tty_driver(UART_NR);
return -ENOMEM;
tty_drv->driver_name = "sdio_uart";
tty_drv->major = 0; /* dynamically allocated */
tty_drv->type = TTY_DRIVER_TYPE_SERIAL;
tty_drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
tty_drv->init_termios.c_cflag = B4800 | CS8 | CREAD | HUPCL | CLOCAL;
tty_drv->init_termios.c_ospeed = 4800;
if (ret)
if (ret)
tty_unregister_driver(tty_drv);
put_tty_driver(tty_drv);
}
static void __exit sdio_uart_exit(void)
sdio_unregister_driver(&sdio_uart_driver);
put_tty_driver(sdio_uart_tty_driver);
module_exit(sdio_uart_exit);
MODULE_AUTHOR("Nicolas Pitre");
MODULE_LICENSE("GPL");