diff options
Diffstat (limited to 'drivers/staging/greybus/uart.c')
-rw-r--r-- | drivers/staging/greybus/uart.c | 1075 |
1 files changed, 1075 insertions, 0 deletions
diff --git a/drivers/staging/greybus/uart.c b/drivers/staging/greybus/uart.c new file mode 100644 index 000000000000..5ee7954bd9f9 --- /dev/null +++ b/drivers/staging/greybus/uart.c @@ -0,0 +1,1075 @@ +/* + * UART driver for the Greybus "generic" UART module. + * + * Copyright 2014 Google Inc. + * Copyright 2014 Linaro Ltd. + * + * Released under the GPLv2 only. + * + * Heavily based on drivers/usb/class/cdc-acm.c and + * drivers/usb/serial/usb-serial.c. + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/mutex.h> +#include <linux/tty.h> +#include <linux/serial.h> +#include <linux/tty_driver.h> +#include <linux/tty_flip.h> +#include <linux/serial.h> +#include <linux/idr.h> +#include <linux/fs.h> +#include <linux/kdev_t.h> +#include <linux/kfifo.h> +#include <linux/workqueue.h> +#include <linux/completion.h> + +#include "greybus.h" +#include "gbphy.h" + +#define GB_NUM_MINORS 16 /* 16 is is more than enough */ +#define GB_NAME "ttyGB" + +#define GB_UART_WRITE_FIFO_SIZE PAGE_SIZE +#define GB_UART_WRITE_ROOM_MARGIN 1 /* leave some space in fifo */ +#define GB_UART_FIRMWARE_CREDITS 4096 +#define GB_UART_CREDIT_WAIT_TIMEOUT_MSEC 10000 + +struct gb_tty_line_coding { + __le32 rate; + __u8 format; + __u8 parity; + __u8 data_bits; + __u8 flow_control; +}; + +struct gb_tty { + struct gbphy_device *gbphy_dev; + struct tty_port port; + void *buffer; + size_t buffer_payload_max; + struct gb_connection *connection; + u16 cport_id; + unsigned int minor; + unsigned char clocal; + bool disconnected; + spinlock_t read_lock; + spinlock_t write_lock; + struct async_icount iocount; + struct async_icount oldcount; + wait_queue_head_t wioctl; + struct mutex mutex; + u8 ctrlin; /* input control lines */ + u8 ctrlout; /* output control lines */ + struct gb_tty_line_coding line_coding; + struct work_struct tx_work; + struct kfifo write_fifo; + bool close_pending; + unsigned int credits; + struct completion credits_complete; +}; + +static struct tty_driver *gb_tty_driver; +static DEFINE_IDR(tty_minors); +static DEFINE_MUTEX(table_lock); + +static int gb_uart_receive_data_handler(struct gb_operation *op) +{ + struct gb_connection *connection = op->connection; + struct gb_tty *gb_tty = gb_connection_get_data(connection); + struct tty_port *port = &gb_tty->port; + struct gb_message *request = op->request; + struct gb_uart_recv_data_request *receive_data; + u16 recv_data_size; + int count; + unsigned long tty_flags = TTY_NORMAL; + + if (request->payload_size < sizeof(*receive_data)) { + dev_err(&gb_tty->gbphy_dev->dev, + "short receive-data request received (%zu < %zu)\n", + request->payload_size, sizeof(*receive_data)); + return -EINVAL; + } + + receive_data = op->request->payload; + recv_data_size = le16_to_cpu(receive_data->size); + + if (recv_data_size != request->payload_size - sizeof(*receive_data)) { + dev_err(&gb_tty->gbphy_dev->dev, + "malformed receive-data request received (%u != %zu)\n", + recv_data_size, + request->payload_size - sizeof(*receive_data)); + return -EINVAL; + } + + if (!recv_data_size) + return -EINVAL; + + if (receive_data->flags) { + if (receive_data->flags & GB_UART_RECV_FLAG_BREAK) + tty_flags = TTY_BREAK; + else if (receive_data->flags & GB_UART_RECV_FLAG_PARITY) + tty_flags = TTY_PARITY; + else if (receive_data->flags & GB_UART_RECV_FLAG_FRAMING) + tty_flags = TTY_FRAME; + + /* overrun is special, not associated with a char */ + if (receive_data->flags & GB_UART_RECV_FLAG_OVERRUN) + tty_insert_flip_char(port, 0, TTY_OVERRUN); + } + count = tty_insert_flip_string_fixed_flag(port, receive_data->data, + tty_flags, recv_data_size); + if (count != recv_data_size) { + dev_err(&gb_tty->gbphy_dev->dev, + "UART: RX 0x%08x bytes only wrote 0x%08x\n", + recv_data_size, count); + } + if (count) + tty_flip_buffer_push(port); + return 0; +} + +static int gb_uart_serial_state_handler(struct gb_operation *op) +{ + struct gb_connection *connection = op->connection; + struct gb_tty *gb_tty = gb_connection_get_data(connection); + struct gb_message *request = op->request; + struct gb_uart_serial_state_request *serial_state; + + if (request->payload_size < sizeof(*serial_state)) { + dev_err(&gb_tty->gbphy_dev->dev, + "short serial-state event received (%zu < %zu)\n", + request->payload_size, sizeof(*serial_state)); + return -EINVAL; + } + + serial_state = request->payload; + gb_tty->ctrlin = serial_state->control; + + return 0; +} + +static int gb_uart_receive_credits_handler(struct gb_operation *op) +{ + struct gb_connection *connection = op->connection; + struct gb_tty *gb_tty = gb_connection_get_data(connection); + struct gb_message *request = op->request; + struct gb_uart_receive_credits_request *credit_request; + unsigned long flags; + unsigned int incoming_credits; + int ret = 0; + + if (request->payload_size < sizeof(*credit_request)) { + dev_err(&gb_tty->gbphy_dev->dev, + "short receive_credits event received (%zu < %zu)\n", + request->payload_size, + sizeof(*credit_request)); + return -EINVAL; + } + + credit_request = request->payload; + incoming_credits = le16_to_cpu(credit_request->count); + + spin_lock_irqsave(&gb_tty->write_lock, flags); + gb_tty->credits += incoming_credits; + if (gb_tty->credits > GB_UART_FIRMWARE_CREDITS) { + gb_tty->credits -= incoming_credits; + ret = -EINVAL; + } + spin_unlock_irqrestore(&gb_tty->write_lock, flags); + + if (ret) { + dev_err(&gb_tty->gbphy_dev->dev, + "invalid number of incoming credits: %d\n", + incoming_credits); + return ret; + } + + if (!gb_tty->close_pending) + schedule_work(&gb_tty->tx_work); + + /* + * the port the tty layer may be waiting for credits + */ + tty_port_tty_wakeup(&gb_tty->port); + + if (gb_tty->credits == GB_UART_FIRMWARE_CREDITS) + complete(&gb_tty->credits_complete); + + return ret; +} + +static int gb_uart_request_handler(struct gb_operation *op) +{ + struct gb_connection *connection = op->connection; + struct gb_tty *gb_tty = gb_connection_get_data(connection); + int type = op->type; + int ret; + + switch (type) { + case GB_UART_TYPE_RECEIVE_DATA: + ret = gb_uart_receive_data_handler(op); + break; + case GB_UART_TYPE_SERIAL_STATE: + ret = gb_uart_serial_state_handler(op); + break; + case GB_UART_TYPE_RECEIVE_CREDITS: + ret = gb_uart_receive_credits_handler(op); + break; + default: + dev_err(&gb_tty->gbphy_dev->dev, + "unsupported unsolicited request: 0x%02x\n", type); + ret = -EINVAL; + } + + return ret; +} + +static void gb_uart_tx_write_work(struct work_struct *work) +{ + struct gb_uart_send_data_request *request; + struct gb_tty *gb_tty; + unsigned long flags; + unsigned int send_size; + int ret; + + gb_tty = container_of(work, struct gb_tty, tx_work); + request = gb_tty->buffer; + + while (1) { + if (gb_tty->close_pending) + break; + + spin_lock_irqsave(&gb_tty->write_lock, flags); + send_size = gb_tty->buffer_payload_max; + if (send_size > gb_tty->credits) + send_size = gb_tty->credits; + + send_size = kfifo_out_peek(&gb_tty->write_fifo, + &request->data[0], + send_size); + if (!send_size) { + spin_unlock_irqrestore(&gb_tty->write_lock, flags); + break; + } + + gb_tty->credits -= send_size; + spin_unlock_irqrestore(&gb_tty->write_lock, flags); + + request->size = cpu_to_le16(send_size); + ret = gb_operation_sync(gb_tty->connection, + GB_UART_TYPE_SEND_DATA, + request, sizeof(*request) + send_size, + NULL, 0); + if (ret) { + dev_err(&gb_tty->gbphy_dev->dev, + "send data error: %d\n", ret); + spin_lock_irqsave(&gb_tty->write_lock, flags); + gb_tty->credits += send_size; + spin_unlock_irqrestore(&gb_tty->write_lock, flags); + if (!gb_tty->close_pending) + schedule_work(work); + return; + } + + spin_lock_irqsave(&gb_tty->write_lock, flags); + ret = kfifo_out(&gb_tty->write_fifo, &request->data[0], + send_size); + spin_unlock_irqrestore(&gb_tty->write_lock, flags); + + tty_port_tty_wakeup(&gb_tty->port); + } +} + +static int send_line_coding(struct gb_tty *tty) +{ + struct gb_uart_set_line_coding_request request; + + memcpy(&request, &tty->line_coding, + sizeof(tty->line_coding)); + return gb_operation_sync(tty->connection, GB_UART_TYPE_SET_LINE_CODING, + &request, sizeof(request), NULL, 0); +} + +static int send_control(struct gb_tty *gb_tty, u8 control) +{ + struct gb_uart_set_control_line_state_request request; + + request.control = control; + return gb_operation_sync(gb_tty->connection, + GB_UART_TYPE_SET_CONTROL_LINE_STATE, + &request, sizeof(request), NULL, 0); +} + +static int send_break(struct gb_tty *gb_tty, u8 state) +{ + struct gb_uart_set_break_request request; + + if ((state != 0) && (state != 1)) { + dev_err(&gb_tty->gbphy_dev->dev, + "invalid break state of %d\n", state); + return -EINVAL; + } + + request.state = state; + return gb_operation_sync(gb_tty->connection, GB_UART_TYPE_SEND_BREAK, + &request, sizeof(request), NULL, 0); +} + +static int gb_uart_wait_for_all_credits(struct gb_tty *gb_tty) +{ + int ret; + + if (gb_tty->credits == GB_UART_FIRMWARE_CREDITS) + return 0; + + ret = wait_for_completion_timeout(&gb_tty->credits_complete, + msecs_to_jiffies(GB_UART_CREDIT_WAIT_TIMEOUT_MSEC)); + if (!ret) { + dev_err(&gb_tty->gbphy_dev->dev, + "time out waiting for credits\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int gb_uart_flush(struct gb_tty *gb_tty, u8 flags) +{ + struct gb_uart_serial_flush_request request; + + request.flags = flags; + return gb_operation_sync(gb_tty->connection, GB_UART_TYPE_FLUSH_FIFOS, + &request, sizeof(request), NULL, 0); +} + +static struct gb_tty *get_gb_by_minor(unsigned int minor) +{ + struct gb_tty *gb_tty; + + mutex_lock(&table_lock); + gb_tty = idr_find(&tty_minors, minor); + if (gb_tty) { + mutex_lock(&gb_tty->mutex); + if (gb_tty->disconnected) { + mutex_unlock(&gb_tty->mutex); + gb_tty = NULL; + } else { + tty_port_get(&gb_tty->port); + mutex_unlock(&gb_tty->mutex); + } + } + mutex_unlock(&table_lock); + return gb_tty; +} + +static int alloc_minor(struct gb_tty *gb_tty) +{ + int minor; + + mutex_lock(&table_lock); + minor = idr_alloc(&tty_minors, gb_tty, 0, GB_NUM_MINORS, GFP_KERNEL); + mutex_unlock(&table_lock); + if (minor >= 0) + gb_tty->minor = minor; + return minor; +} + +static void release_minor(struct gb_tty *gb_tty) +{ + int minor = gb_tty->minor; + + gb_tty->minor = 0; /* Maybe should use an invalid value instead */ + mutex_lock(&table_lock); + idr_remove(&tty_minors, minor); + mutex_unlock(&table_lock); +} + +static int gb_tty_install(struct tty_driver *driver, struct tty_struct *tty) +{ + struct gb_tty *gb_tty; + int retval; + + gb_tty = get_gb_by_minor(tty->index); + if (!gb_tty) + return -ENODEV; + + retval = tty_standard_install(driver, tty); + if (retval) + goto error; + + tty->driver_data = gb_tty; + return 0; +error: + tty_port_put(&gb_tty->port); + return retval; +} + +static int gb_tty_open(struct tty_struct *tty, struct file *file) +{ + struct gb_tty *gb_tty = tty->driver_data; + + return tty_port_open(&gb_tty->port, tty, file); +} + +static void gb_tty_close(struct tty_struct *tty, struct file *file) +{ + struct gb_tty *gb_tty = tty->driver_data; + + tty_port_close(&gb_tty->port, tty, file); +} + +static void gb_tty_cleanup(struct tty_struct *tty) +{ + struct gb_tty *gb_tty = tty->driver_data; + + tty_port_put(&gb_tty->port); +} + +static void gb_tty_hangup(struct tty_struct *tty) +{ + struct gb_tty *gb_tty = tty->driver_data; + + tty_port_hangup(&gb_tty->port); +} + +static int gb_tty_write(struct tty_struct *tty, const unsigned char *buf, + int count) +{ + struct gb_tty *gb_tty = tty->driver_data; + + count = kfifo_in_spinlocked(&gb_tty->write_fifo, buf, count, + &gb_tty->write_lock); + if (count && !gb_tty->close_pending) + schedule_work(&gb_tty->tx_work); + + return count; +} + +static int gb_tty_write_room(struct tty_struct *tty) +{ + struct gb_tty *gb_tty = tty->driver_data; + unsigned long flags; + int room; + + spin_lock_irqsave(&gb_tty->write_lock, flags); + room = kfifo_avail(&gb_tty->write_fifo); + spin_unlock_irqrestore(&gb_tty->write_lock, flags); + + room -= GB_UART_WRITE_ROOM_MARGIN; + if (room < 0) + return 0; + + return room; +} + +static int gb_tty_chars_in_buffer(struct tty_struct *tty) +{ + struct gb_tty *gb_tty = tty->driver_data; + unsigned long flags; + int chars; + + spin_lock_irqsave(&gb_tty->write_lock, flags); + chars = kfifo_len(&gb_tty->write_fifo); + if (gb_tty->credits < GB_UART_FIRMWARE_CREDITS) + chars += GB_UART_FIRMWARE_CREDITS - gb_tty->credits; + spin_unlock_irqrestore(&gb_tty->write_lock, flags); + + return chars; +} + +static int gb_tty_break_ctl(struct tty_struct *tty, int state) +{ + struct gb_tty *gb_tty = tty->driver_data; + + return send_break(gb_tty, state ? 1 : 0); +} + +static void gb_tty_set_termios(struct tty_struct *tty, + struct ktermios *termios_old) +{ + struct gb_tty *gb_tty = tty->driver_data; + struct ktermios *termios = &tty->termios; + struct gb_tty_line_coding newline; + u8 newctrl = gb_tty->ctrlout; + + newline.rate = cpu_to_le32(tty_get_baud_rate(tty)); + newline.format = termios->c_cflag & CSTOPB ? + GB_SERIAL_2_STOP_BITS : GB_SERIAL_1_STOP_BITS; + newline.parity = termios->c_cflag & PARENB ? + (termios->c_cflag & PARODD ? 1 : 2) + + (termios->c_cflag & CMSPAR ? 2 : 0) : 0; + + switch (termios->c_cflag & CSIZE) { + case CS5: + newline.data_bits = 5; + break; + case CS6: + newline.data_bits = 6; + break; + case CS7: + newline.data_bits = 7; + break; + case CS8: + default: + newline.data_bits = 8; + break; + } + + /* FIXME: needs to clear unsupported bits in the termios */ + gb_tty->clocal = ((termios->c_cflag & CLOCAL) != 0); + + if (C_BAUD(tty) == B0) { + newline.rate = gb_tty->line_coding.rate; + newctrl &= ~(GB_UART_CTRL_DTR | GB_UART_CTRL_RTS); + } else if (termios_old && (termios_old->c_cflag & CBAUD) == B0) { + newctrl |= (GB_UART_CTRL_DTR | GB_UART_CTRL_RTS); + } + + if (newctrl != gb_tty->ctrlout) { + gb_tty->ctrlout = newctrl; + send_control(gb_tty, newctrl); + } + + if (C_CRTSCTS(tty) && C_BAUD(tty) != B0) + newline.flow_control |= GB_SERIAL_AUTO_RTSCTS_EN; + else + newline.flow_control &= ~GB_SERIAL_AUTO_RTSCTS_EN; + + if (memcmp(&gb_tty->line_coding, &newline, sizeof(newline))) { + memcpy(&gb_tty->line_coding, &newline, sizeof(newline)); + send_line_coding(gb_tty); + } +} + +static int gb_tty_tiocmget(struct tty_struct *tty) +{ + struct gb_tty *gb_tty = tty->driver_data; + + return (gb_tty->ctrlout & GB_UART_CTRL_DTR ? TIOCM_DTR : 0) | + (gb_tty->ctrlout & GB_UART_CTRL_RTS ? TIOCM_RTS : 0) | + (gb_tty->ctrlin & GB_UART_CTRL_DSR ? TIOCM_DSR : 0) | + (gb_tty->ctrlin & GB_UART_CTRL_RI ? TIOCM_RI : 0) | + (gb_tty->ctrlin & GB_UART_CTRL_DCD ? TIOCM_CD : 0) | + TIOCM_CTS; +} + +static int gb_tty_tiocmset(struct tty_struct *tty, unsigned int set, + unsigned int clear) +{ + struct gb_tty *gb_tty = tty->driver_data; + u8 newctrl = gb_tty->ctrlout; + + set = (set & TIOCM_DTR ? GB_UART_CTRL_DTR : 0) | + (set & TIOCM_RTS ? GB_UART_CTRL_RTS : 0); + clear = (clear & TIOCM_DTR ? GB_UART_CTRL_DTR : 0) | + (clear & TIOCM_RTS ? GB_UART_CTRL_RTS : 0); + + newctrl = (newctrl & ~clear) | set; + if (gb_tty->ctrlout == newctrl) + return 0; + + gb_tty->ctrlout = newctrl; + return send_control(gb_tty, newctrl); +} + +static void gb_tty_throttle(struct tty_struct *tty) +{ + struct gb_tty *gb_tty = tty->driver_data; + unsigned char stop_char; + int retval; + + if (I_IXOFF(tty)) { + stop_char = STOP_CHAR(tty); + retval = gb_tty_write(tty, &stop_char, 1); + if (retval <= 0) + return; + } + + if (tty->termios.c_cflag & CRTSCTS) { + gb_tty->ctrlout &= ~GB_UART_CTRL_RTS; + retval = send_control(gb_tty, gb_tty->ctrlout); + } +} + +static void gb_tty_unthrottle(struct tty_struct *tty) +{ + struct gb_tty *gb_tty = tty->driver_data; + unsigned char start_char; + int retval; + + if (I_IXOFF(tty)) { + start_char = START_CHAR(tty); + retval = gb_tty_write(tty, &start_char, 1); + if (retval <= 0) + return; + } + + if (tty->termios.c_cflag & CRTSCTS) { + gb_tty->ctrlout |= GB_UART_CTRL_RTS; + retval = send_control(gb_tty, gb_tty->ctrlout); + } +} + +static int get_serial_info(struct gb_tty *gb_tty, + struct serial_struct __user *info) +{ + struct serial_struct tmp; + + if (!info) + return -EINVAL; + + memset(&tmp, 0, sizeof(tmp)); + tmp.flags = ASYNC_LOW_LATENCY | ASYNC_SKIP_TEST; + tmp.type = PORT_16550A; + tmp.line = gb_tty->minor; + tmp.xmit_fifo_size = 16; + tmp.baud_base = 9600; + tmp.close_delay = gb_tty->port.close_delay / 10; + tmp.closing_wait = gb_tty->port.closing_wait == ASYNC_CLOSING_WAIT_NONE ? + ASYNC_CLOSING_WAIT_NONE : gb_tty->port.closing_wait / 10; + + if (copy_to_user(info, &tmp, sizeof(tmp))) + return -EFAULT; + return 0; +} + +static int set_serial_info(struct gb_tty *gb_tty, + struct serial_struct __user *newinfo) +{ + struct serial_struct new_serial; + unsigned int closing_wait; + unsigned int close_delay; + int retval = 0; + + if (copy_from_user(&new_serial, newinfo, sizeof(new_serial))) + return -EFAULT; + + close_delay = new_serial.close_delay * 10; + closing_wait = new_serial.closing_wait == ASYNC_CLOSING_WAIT_NONE ? + ASYNC_CLOSING_WAIT_NONE : new_serial.closing_wait * 10; + + mutex_lock(&gb_tty->port.mutex); + if (!capable(CAP_SYS_ADMIN)) { + if ((close_delay != gb_tty->port.close_delay) || + (closing_wait != gb_tty->port.closing_wait)) + retval = -EPERM; + else + retval = -EOPNOTSUPP; + } else { + gb_tty->port.close_delay = close_delay; + gb_tty->port.closing_wait = closing_wait; + } + mutex_unlock(&gb_tty->port.mutex); + return retval; +} + +static int wait_serial_change(struct gb_tty *gb_tty, unsigned long arg) +{ + int retval = 0; + DECLARE_WAITQUEUE(wait, current); + struct async_icount old; + struct async_icount new; + + if (!(arg & (TIOCM_DSR | TIOCM_RI | TIOCM_CD))) + return -EINVAL; + + do { + spin_lock_irq(&gb_tty->read_lock); + old = gb_tty->oldcount; + new = gb_tty->iocount; + gb_tty->oldcount = new; + spin_unlock_irq(&gb_tty->read_lock); + + if ((arg & TIOCM_DSR) && (old.dsr != new.dsr)) + break; + if ((arg & TIOCM_CD) && (old.dcd != new.dcd)) + break; + if ((arg & TIOCM_RI) && (old.rng != new.rng)) + break; + + add_wait_queue(&gb_tty->wioctl, &wait); + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + remove_wait_queue(&gb_tty->wioctl, &wait); + if (gb_tty->disconnected) { + if (arg & TIOCM_CD) + break; + retval = -ENODEV; + } else if (signal_pending(current)) { + retval = -ERESTARTSYS; + } + } while (!retval); + + return retval; +} + +static int get_serial_usage(struct gb_tty *gb_tty, + struct serial_icounter_struct __user *count) +{ + struct serial_icounter_struct icount; + int retval = 0; + + memset(&icount, 0, sizeof(icount)); + icount.dsr = gb_tty->iocount.dsr; + icount.rng = gb_tty->iocount.rng; + icount.dcd = gb_tty->iocount.dcd; + icount.frame = gb_tty->iocount.frame; + icount.overrun = gb_tty->iocount.overrun; + icount.parity = gb_tty->iocount.parity; + icount.brk = gb_tty->iocount.brk; + + if (copy_to_user(count, &icount, sizeof(icount)) > 0) + retval = -EFAULT; + + return retval; +} + +static int gb_tty_ioctl(struct tty_struct *tty, unsigned int cmd, + unsigned long arg) +{ + struct gb_tty *gb_tty = tty->driver_data; + + switch (cmd) { + case TIOCGSERIAL: + return get_serial_info(gb_tty, + (struct serial_struct __user *)arg); + case TIOCSSERIAL: + return set_serial_info(gb_tty, + (struct serial_struct __user *)arg); + case TIOCMIWAIT: + return wait_serial_change(gb_tty, arg); + case TIOCGICOUNT: + return get_serial_usage(gb_tty, + (struct serial_icounter_struct __user *)arg); + } + + return -ENOIOCTLCMD; +} + +static void gb_tty_dtr_rts(struct tty_port *port, int on) +{ + struct gb_tty *gb_tty; + u8 newctrl; + + gb_tty = container_of(port, struct gb_tty, port); + newctrl = gb_tty->ctrlout; + + if (on) + newctrl |= (GB_UART_CTRL_DTR | GB_UART_CTRL_RTS); + else + newctrl &= ~(GB_UART_CTRL_DTR | GB_UART_CTRL_RTS); + + gb_tty->ctrlout = newctrl; + send_control(gb_tty, newctrl); +} + +static int gb_tty_port_activate(struct tty_port *port, + struct tty_struct *tty) +{ + struct gb_tty *gb_tty; + + gb_tty = container_of(port, struct gb_tty, port); + + return gbphy_runtime_get_sync(gb_tty->gbphy_dev); +} + +static void gb_tty_port_shutdown(struct tty_port *port) +{ + struct gb_tty *gb_tty; + unsigned long flags; + int ret; + + gb_tty = container_of(port, struct gb_tty, port); + + gb_tty->close_pending = true; + + cancel_work_sync(&gb_tty->tx_work); + + spin_lock_irqsave(&gb_tty->write_lock, flags); + kfifo_reset_out(&gb_tty->write_fifo); + spin_unlock_irqrestore(&gb_tty->write_lock, flags); + + if (gb_tty->credits == GB_UART_FIRMWARE_CREDITS) + goto out; + + ret = gb_uart_flush(gb_tty, GB_SERIAL_FLAG_FLUSH_TRANSMITTER); + if (ret) { + dev_err(&gb_tty->gbphy_dev->dev, + "error flushing transmitter: %d\n", ret); + } + + gb_uart_wait_for_all_credits(gb_tty); + +out: + gb_tty->close_pending = false; + + gbphy_runtime_put_autosuspend(gb_tty->gbphy_dev); +} + +static const struct tty_operations gb_ops = { + .install = gb_tty_install, + .open = gb_tty_open, + .close = gb_tty_close, + .cleanup = gb_tty_cleanup, + .hangup = gb_tty_hangup, + .write = gb_tty_write, + .write_room = gb_tty_write_room, + .ioctl = gb_tty_ioctl, + .throttle = gb_tty_throttle, + .unthrottle = gb_tty_unthrottle, + .chars_in_buffer = gb_tty_chars_in_buffer, + .break_ctl = gb_tty_break_ctl, + .set_termios = gb_tty_set_termios, + .tiocmget = gb_tty_tiocmget, + .tiocmset = gb_tty_tiocmset, +}; + +static struct tty_port_operations gb_port_ops = { + .dtr_rts = gb_tty_dtr_rts, + .activate = gb_tty_port_activate, + .shutdown = gb_tty_port_shutdown, +}; + +static int gb_uart_probe(struct gbphy_device *gbphy_dev, + const struct gbphy_device_id *id) +{ + struct gb_connection *connection; + size_t max_payload; + struct gb_tty *gb_tty; + struct device *tty_dev; + int retval; + int minor; + + gb_tty = kzalloc(sizeof(*gb_tty), GFP_KERNEL); + if (!gb_tty) + return -ENOMEM; + + connection = gb_connection_create(gbphy_dev->bundle, + le16_to_cpu(gbphy_dev->cport_desc->id), + gb_uart_request_handler); + if (IS_ERR(connection)) { + retval = PTR_ERR(connection); + goto exit_tty_free; + } + + max_payload = gb_operation_get_payload_size_max(connection); + if (max_payload < sizeof(struct gb_uart_send_data_request)) { + retval = -EINVAL; + goto exit_connection_destroy; + } + + gb_tty->buffer_payload_max = max_payload - + sizeof(struct gb_uart_send_data_request); + + gb_tty->buffer = kzalloc(gb_tty->buffer_payload_max, GFP_KERNEL); + if (!gb_tty->buffer) { + retval = -ENOMEM; + goto exit_connection_destroy; + } + + INIT_WORK(&gb_tty->tx_work, gb_uart_tx_write_work); + + retval = kfifo_alloc(&gb_tty->write_fifo, GB_UART_WRITE_FIFO_SIZE, + GFP_KERNEL); + if (retval) + goto exit_buf_free; + + gb_tty->credits = GB_UART_FIRMWARE_CREDITS; + init_completion(&gb_tty->credits_complete); + + minor = alloc_minor(gb_tty); + if (minor < 0) { + if (minor == -ENOSPC) { + dev_err(&connection->bundle->dev, + "no more free minor numbers\n"); + retval = -ENODEV; + } else { + retval = minor; + } + goto exit_kfifo_free; + } + + gb_tty->minor = minor; + spin_lock_init(&gb_tty->write_lock); + spin_lock_init(&gb_tty->read_lock); + init_waitqueue_head(&gb_tty->wioctl); + mutex_init(&gb_tty->mutex); + + tty_port_init(&gb_tty->port); + gb_tty->port.ops = &gb_port_ops; + + gb_tty->connection = connection; + gb_tty->gbphy_dev = gbphy_dev; + gb_connection_set_data(connection, gb_tty); + gb_gbphy_set_data(gbphy_dev, gb_tty); + + retval = gb_connection_enable_tx(connection); + if (retval) + goto exit_release_minor; + + send_control(gb_tty, gb_tty->ctrlout); + + /* initialize the uart to be 9600n81 */ + gb_tty->line_coding.rate = cpu_to_le32(9600); + gb_tty->line_coding.format = GB_SERIAL_1_STOP_BITS; + gb_tty->line_coding.parity = GB_SERIAL_NO_PARITY; + gb_tty->line_coding.data_bits = 8; + send_line_coding(gb_tty); + + retval = gb_connection_enable(connection); + if (retval) + goto exit_connection_disable; + + tty_dev = tty_port_register_device(&gb_tty->port, gb_tty_driver, minor, + &gbphy_dev->dev); + if (IS_ERR(tty_dev)) { + retval = PTR_ERR(tty_dev); + goto exit_connection_disable; + } + + gbphy_runtime_put_autosuspend(gbphy_dev); + return 0; + +exit_connection_disable: + gb_connection_disable(connection); +exit_release_minor: + release_minor(gb_tty); +exit_kfifo_free: + kfifo_free(&gb_tty->write_fifo); +exit_buf_free: + kfree(gb_tty->buffer); +exit_connection_destroy: + gb_connection_destroy(connection); +exit_tty_free: + kfree(gb_tty); + + return retval; +} + +static void gb_uart_remove(struct gbphy_device *gbphy_dev) +{ + struct gb_tty *gb_tty = gb_gbphy_get_data(gbphy_dev); + struct gb_connection *connection = gb_tty->connection; + struct tty_struct *tty; + int ret; + + ret = gbphy_runtime_get_sync(gbphy_dev); + if (ret) + gbphy_runtime_get_noresume(gbphy_dev); + + mutex_lock(&gb_tty->mutex); + gb_tty->disconnected = true; + + wake_up_all(&gb_tty->wioctl); + mutex_unlock(&gb_tty->mutex); + + tty = tty_port_tty_get(&gb_tty->port); + if (tty) { + tty_vhangup(tty); + tty_kref_put(tty); + } + + gb_connection_disable_rx(connection); + tty_unregister_device(gb_tty_driver, gb_tty->minor); + + /* FIXME - free transmit / receive buffers */ + + gb_connection_disable(connection); + tty_port_destroy(&gb_tty->port); + gb_connection_destroy(connection); + release_minor(gb_tty); + kfifo_free(&gb_tty->write_fifo); + kfree(gb_tty->buffer); + kfree(gb_tty); +} + +static int gb_tty_init(void) +{ + int retval = 0; + + gb_tty_driver = tty_alloc_driver(GB_NUM_MINORS, 0); + if (IS_ERR(gb_tty_driver)) { + pr_err("Can not allocate tty driver\n"); + retval = -ENOMEM; + goto fail_unregister_dev; + } + + gb_tty_driver->driver_name = "gb"; + gb_tty_driver->name = GB_NAME; + gb_tty_driver->major = 0; + gb_tty_driver->minor_start = 0; + gb_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; + gb_tty_driver->subtype = SERIAL_TYPE_NORMAL; + gb_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; + gb_tty_driver->init_termios = tty_std_termios; + gb_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; + tty_set_operations(gb_tty_driver, &gb_ops); + + retval = tty_register_driver(gb_tty_driver); + if (retval) { + pr_err("Can not register tty driver: %d\n", retval); + goto fail_put_gb_tty; + } + + return 0; + +fail_put_gb_tty: + put_tty_driver(gb_tty_driver); +fail_unregister_dev: + return retval; +} + +static void gb_tty_exit(void) +{ + tty_unregister_driver(gb_tty_driver); + put_tty_driver(gb_tty_driver); + idr_destroy(&tty_minors); +} + +static const struct gbphy_device_id gb_uart_id_table[] = { + { GBPHY_PROTOCOL(GREYBUS_PROTOCOL_UART) }, + { }, +}; +MODULE_DEVICE_TABLE(gbphy, gb_uart_id_table); + +static struct gbphy_driver uart_driver = { + .name = "uart", + .probe = gb_uart_probe, + .remove = gb_uart_remove, + .id_table = gb_uart_id_table, +}; + +static int gb_uart_driver_init(void) +{ + int ret; + + ret = gb_tty_init(); + if (ret) + return ret; + + ret = gb_gbphy_register(&uart_driver); + if (ret) { + gb_tty_exit(); + return ret; + } + + return 0; +} +module_init(gb_uart_driver_init); + +static void gb_uart_driver_exit(void) +{ + gb_gbphy_deregister(&uart_driver); + gb_tty_exit(); +} + +module_exit(gb_uart_driver_exit); +MODULE_LICENSE("GPL v2"); |