diff options
Diffstat (limited to 'drivers/tty')
| -rw-r--r-- | drivers/tty/Kconfig | 47 | ||||
| -rw-r--r-- | drivers/tty/Makefile | 1 | ||||
| -rw-r--r-- | drivers/tty/bfin_jtag_comm.c | 13 | ||||
| -rw-r--r-- | drivers/tty/goldfish.c | 2 | ||||
| -rw-r--r-- | drivers/tty/ipwireless/hardware.c | 2 | ||||
| -rw-r--r-- | drivers/tty/mips_ejtag_fdc.c | 1303 | ||||
| -rw-r--r-- | drivers/tty/serial/8250/8250_core.c | 11 | ||||
| -rw-r--r-- | drivers/tty/serial/8250/8250_dw.c | 47 | ||||
| -rw-r--r-- | drivers/tty/serial/8250/8250_fintek.c | 13 | ||||
| -rw-r--r-- | drivers/tty/serial/8250/8250_pci.c | 20 | ||||
| -rw-r--r-- | drivers/tty/serial/atmel_serial.c | 49 | ||||
| -rw-r--r-- | drivers/tty/serial/fsl_lpuart.c | 5 | ||||
| -rw-r--r-- | drivers/tty/serial/of_serial.c | 4 | ||||
| -rw-r--r-- | drivers/tty/serial/samsung.c | 1 | ||||
| -rw-r--r-- | drivers/tty/serial/sprd_serial.c | 4 | ||||
| -rw-r--r-- | drivers/tty/sysrq.c | 1 | ||||
| -rw-r--r-- | drivers/tty/tty_io.c | 4 | ||||
| -rw-r--r-- | drivers/tty/tty_ioctl.c | 16 |
18 files changed, 1471 insertions, 72 deletions
diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig index b24aa010f68c..c01f45095877 100644 --- a/drivers/tty/Kconfig +++ b/drivers/tty/Kconfig @@ -419,4 +419,51 @@ config DA_CONSOLE help This enables a console on a Dash channel. +config MIPS_EJTAG_FDC_TTY + bool "MIPS EJTAG Fast Debug Channel TTY" + depends on MIPS_CDMM + help + This enables a TTY and console on the MIPS EJTAG Fast Debug Channels, + if they are present. This can be useful when working with an EJTAG + probe which supports it, to get console output and a login prompt via + EJTAG without needing to connect a serial cable. + + TTY devices are named e.g. ttyFDC3c2 (for FDC channel 2 of the FDC on + CPU3). + + The console can be enabled with console=fdc1 (for FDC channel 1 on all + CPUs). Do not use the console unless there is a debug probe attached + to drain the FDC TX FIFO. + + If unsure, say N. + +config MIPS_EJTAG_FDC_EARLYCON + bool "Early FDC console" + depends on MIPS_EJTAG_FDC_TTY + help + This registers a console on FDC channel 1 very early during boot (from + MIPS arch code). This is useful for bring-up and debugging early boot + issues. + + Do not enable unless there is a debug probe attached to drain the FDC + TX FIFO. + + If unsure, say N. + +config MIPS_EJTAG_FDC_KGDB + bool "Use KGDB over an FDC channel" + depends on MIPS_EJTAG_FDC_TTY && KGDB + default y + help + This enables the use of KGDB over an FDC channel, allowing KGDB to be + used remotely or when a serial port isn't available. + +config MIPS_EJTAG_FDC_KGDB_CHAN + int "KGDB FDC channel" + depends on MIPS_EJTAG_FDC_KGDB + range 2 15 + default 3 + help + FDC channel number to use for KGDB. + endif # TTY diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile index 58ad1c05b7f8..5817e2397463 100644 --- a/drivers/tty/Makefile +++ b/drivers/tty/Makefile @@ -29,5 +29,6 @@ obj-$(CONFIG_SYNCLINK) += synclink.o obj-$(CONFIG_PPC_EPAPR_HV_BYTECHAN) += ehv_bytechan.o obj-$(CONFIG_GOLDFISH_TTY) += goldfish.o obj-$(CONFIG_DA_TTY) += metag_da.o +obj-$(CONFIG_MIPS_EJTAG_FDC_TTY) += mips_ejtag_fdc.o obj-y += ipwireless/ diff --git a/drivers/tty/bfin_jtag_comm.c b/drivers/tty/bfin_jtag_comm.c index d7b198c400c7..ce24182f8514 100644 --- a/drivers/tty/bfin_jtag_comm.c +++ b/drivers/tty/bfin_jtag_comm.c @@ -210,18 +210,6 @@ bfin_jc_chars_in_buffer(struct tty_struct *tty) return circ_cnt(&bfin_jc_write_buf); } -static void -bfin_jc_wait_until_sent(struct tty_struct *tty, int timeout) -{ - unsigned long expire = jiffies + timeout; - while (!circ_empty(&bfin_jc_write_buf)) { - if (signal_pending(current)) - break; - if (time_after(jiffies, expire)) - break; - } -} - static const struct tty_operations bfin_jc_ops = { .open = bfin_jc_open, .close = bfin_jc_close, @@ -230,7 +218,6 @@ static const struct tty_operations bfin_jc_ops = { .flush_chars = bfin_jc_flush_chars, .write_room = bfin_jc_write_room, .chars_in_buffer = bfin_jc_chars_in_buffer, - .wait_until_sent = bfin_jc_wait_until_sent, }; static int __init bfin_jc_init(void) diff --git a/drivers/tty/goldfish.c b/drivers/tty/goldfish.c index 967b2c2b7cf1..0655fecf8240 100644 --- a/drivers/tty/goldfish.c +++ b/drivers/tty/goldfish.c @@ -229,7 +229,6 @@ static int goldfish_tty_probe(struct platform_device *pdev) { struct goldfish_tty *qtty; int ret = -EINVAL; - int i; struct resource *r; struct device *ttydev; void __iomem *base; @@ -293,7 +292,6 @@ static int goldfish_tty_probe(struct platform_device *pdev) mutex_unlock(&goldfish_tty_lock); return 0; - tty_unregister_device(goldfish_tty_driver, i); err_tty_register_device_failed: free_irq(irq, pdev); err_request_irq_failed: diff --git a/drivers/tty/ipwireless/hardware.c b/drivers/tty/ipwireless/hardware.c index 5c77e1eac4ee..ad7031a4f3c4 100644 --- a/drivers/tty/ipwireless/hardware.c +++ b/drivers/tty/ipwireless/hardware.c @@ -1455,7 +1455,7 @@ static void __handle_setup_get_version_rsp(struct ipw_hardware *hw) return; } - set_RTS(hw, PRIO_SETUP, channel_idx, + ret = set_RTS(hw, PRIO_SETUP, channel_idx, (hw->control_lines [channel_idx] & IPW_CONTROL_LINE_RTS) != 0); if (ret) { diff --git a/drivers/tty/mips_ejtag_fdc.c b/drivers/tty/mips_ejtag_fdc.c new file mode 100644 index 000000000000..04d9e23d1ee1 --- /dev/null +++ b/drivers/tty/mips_ejtag_fdc.c @@ -0,0 +1,1303 @@ +/* + * TTY driver for MIPS EJTAG Fast Debug Channels. + * + * Copyright (C) 2007-2015 Imagination Technologies Ltd + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for more + * details. + */ + +#include <linux/atomic.h> +#include <linux/bitops.h> +#include <linux/completion.h> +#include <linux/console.h> +#include <linux/delay.h> +#include <linux/export.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/kgdb.h> +#include <linux/kthread.h> +#include <linux/sched.h> +#include <linux/serial.h> +#include <linux/serial_core.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/tty.h> +#include <linux/tty_driver.h> +#include <linux/tty_flip.h> +#include <linux/uaccess.h> + +#include <asm/cdmm.h> +#include <asm/irq.h> + +/* Register offsets */ +#define REG_FDACSR 0x00 /* FDC Access Control and Status Register */ +#define REG_FDCFG 0x08 /* FDC Configuration Register */ +#define REG_FDSTAT 0x10 /* FDC Status Register */ +#define REG_FDRX 0x18 /* FDC Receive Register */ +#define REG_FDTX(N) (0x20+0x8*(N)) /* FDC Transmit Register n (0..15) */ + +/* Register fields */ + +#define REG_FDCFG_TXINTTHRES_SHIFT 18 +#define REG_FDCFG_TXINTTHRES (0x3 << REG_FDCFG_TXINTTHRES_SHIFT) +#define REG_FDCFG_TXINTTHRES_DISABLED (0x0 << REG_FDCFG_TXINTTHRES_SHIFT) +#define REG_FDCFG_TXINTTHRES_EMPTY (0x1 << REG_FDCFG_TXINTTHRES_SHIFT) +#define REG_FDCFG_TXINTTHRES_NOTFULL (0x2 << REG_FDCFG_TXINTTHRES_SHIFT) +#define REG_FDCFG_TXINTTHRES_NEAREMPTY (0x3 << REG_FDCFG_TXINTTHRES_SHIFT) +#define REG_FDCFG_RXINTTHRES_SHIFT 16 +#define REG_FDCFG_RXINTTHRES (0x3 << REG_FDCFG_RXINTTHRES_SHIFT) +#define REG_FDCFG_RXINTTHRES_DISABLED (0x0 << REG_FDCFG_RXINTTHRES_SHIFT) +#define REG_FDCFG_RXINTTHRES_FULL (0x1 << REG_FDCFG_RXINTTHRES_SHIFT) +#define REG_FDCFG_RXINTTHRES_NOTEMPTY (0x2 << REG_FDCFG_RXINTTHRES_SHIFT) +#define REG_FDCFG_RXINTTHRES_NEARFULL (0x3 << REG_FDCFG_RXINTTHRES_SHIFT) +#define REG_FDCFG_TXFIFOSIZE_SHIFT 8 +#define REG_FDCFG_TXFIFOSIZE (0xff << REG_FDCFG_TXFIFOSIZE_SHIFT) +#define REG_FDCFG_RXFIFOSIZE_SHIFT 0 +#define REG_FDCFG_RXFIFOSIZE (0xff << REG_FDCFG_RXFIFOSIZE_SHIFT) + +#define REG_FDSTAT_TXCOUNT_SHIFT 24 +#define REG_FDSTAT_TXCOUNT (0xff << REG_FDSTAT_TXCOUNT_SHIFT) +#define REG_FDSTAT_RXCOUNT_SHIFT 16 +#define REG_FDSTAT_RXCOUNT (0xff << REG_FDSTAT_RXCOUNT_SHIFT) +#define REG_FDSTAT_RXCHAN_SHIFT 4 +#define REG_FDSTAT_RXCHAN (0xf << REG_FDSTAT_RXCHAN_SHIFT) +#define REG_FDSTAT_RXE BIT(3) /* Rx Empty */ +#define REG_FDSTAT_RXF BIT(2) /* Rx Full */ +#define REG_FDSTAT_TXE BIT(1) /* Tx Empty */ +#define REG_FDSTAT_TXF BIT(0) /* Tx Full */ + +/* Default channel for the early console */ +#define CONSOLE_CHANNEL 1 + +#define NUM_TTY_CHANNELS 16 + +#define RX_BUF_SIZE 1024 + +/* + * When the IRQ is unavailable, the FDC state must be polled for incoming data + * and space becoming available in TX FIFO. + */ +#define FDC_TTY_POLL (HZ / 50) + +struct mips_ejtag_fdc_tty; + +/** + * struct mips_ejtag_fdc_tty_port - Wrapper struct for FDC tty_port. + * @port: TTY port data + * @driver: TTY driver. + * @rx_lock: Lock for rx_buf. + * This protects between the hard interrupt and user + * context. It's also held during read SWITCH operations. + * @rx_buf: Read buffer. + * @xmit_lock: Lock for xmit_*, and port.xmit_buf. + * This protects between user context and kernel thread. + * It is used from chars_in_buffer()/write_room() TTY + * callbacks which are used during wait operations, so a + * mutex is unsuitable. + * @xmit_cnt: Size of xmit buffer contents. + * @xmit_head: Head of xmit buffer where data is written. + * @xmit_tail: Tail of xmit buffer where data is read. + * @xmit_empty: Completion for xmit buffer being empty. + */ +struct mips_ejtag_fdc_tty_port { + struct tty_port port; + struct mips_ejtag_fdc_tty *driver; + raw_spinlock_t rx_lock; + void *rx_buf; + spinlock_t xmit_lock; + unsigned int xmit_cnt; + unsigned int xmit_head; + unsigned int xmit_tail; + struct completion xmit_empty; +}; + +/** + * struct mips_ejtag_fdc_tty - Driver data for FDC as a whole. + * @dev: FDC device (for dev_*() logging). + * @driver: TTY driver. + * @cpu: CPU number for this FDC. + * @fdc_name: FDC name (not for base of channel names). + * @driver_name: Base of driver name. + * @ports: Per-channel data. + * @waitqueue: Wait queue for waiting for TX data, or for space in TX + * FIFO. + * @lock: Lock to protect FDCFG (interrupt enable). + * @thread: KThread for writing out data to FDC. + * @reg: FDC registers. + * @tx_fifo: TX FIFO size. + * @xmit_size: Size of each port's xmit buffer. + * @xmit_total: Total number of bytes (from all ports) to transmit. + * @xmit_next: Next port number to transmit from (round robin). + * @xmit_full: Indicates TX FIFO is full, we're waiting for space. + * @irq: IRQ number (negative if no IRQ). + * @removing: Indicates the device is being removed and @poll_timer + * should not be restarted. + * @poll_timer: Timer for polling for interrupt events when @irq < 0. + * @sysrq_pressed: Whether the magic sysrq key combination has been + * detected. See mips_ejtag_fdc_handle(). + */ +struct mips_ejtag_fdc_tty { + struct device *dev; + struct tty_driver *driver; + unsigned int cpu; + char fdc_name[16]; + char driver_name[16]; + struct mips_ejtag_fdc_tty_port ports[NUM_TTY_CHANNELS]; + wait_queue_head_t waitqueue; + raw_spinlock_t lock; + struct task_struct *thread; + + void __iomem *reg; + u8 tx_fifo; + + unsigned int xmit_size; + atomic_t xmit_total; + unsigned int xmit_next; + bool xmit_full; + + int irq; + bool removing; + struct timer_list poll_timer; + +#ifdef CONFIG_MAGIC_SYSRQ + bool sysrq_pressed; +#endif +}; + +/* Hardware access */ + +static inline void mips_ejtag_fdc_write(struct mips_ejtag_fdc_tty *priv, + unsigned int offs, unsigned int data) +{ + iowrite32(data, priv->reg + offs); +} + +static inline unsigned int mips_ejtag_fdc_read(struct mips_ejtag_fdc_tty *priv, + unsigned int offs) +{ + return ioread32(priv->reg + offs); +} + +/* Encoding of byte stream in FDC words */ + +/** + * struct fdc_word - FDC word encoding some number of bytes of data. + * @word: Raw FDC word. + * @bytes: Number of bytes encoded by @word. + */ +struct fdc_word { + u32 word; + unsigned int bytes; +}; + +/* + * This is a compact encoding which allows every 1 byte, 2 byte, and 3 byte + * sequence to be encoded in a single word, while allowing the majority of 4 + * byte sequences (including all ASCII and common binary data) to be encoded in + * a single word too. + * _______________________ _____________ + * | FDC Word | | + * |31-24|23-16|15-8 | 7-0 | Bytes | + * |_____|_____|_____|_____|_____________| + * | | | | | | + * |0x80 |0x80 |0x80 | WW | WW | + * |0x81 |0x81 | XX | WW | WW XX | + * |0x82 | YY | XX | WW | WW XX YY | + * | ZZ | YY | XX | WW | WW XX YY ZZ | + * |_____|_____|_____|_____|_____________| + * + * Note that the 4-byte encoding can only be used where none of the other 3 + * encodings match, otherwise it must fall back to the 3 byte encoding. + */ + +/* ranges >= 1 && sizes[0] >= 1 */ +static struct fdc_word mips_ejtag_fdc_encode(const char **ptrs, + unsigned int *sizes, + unsigned int ranges) +{ + struct fdc_word word = { 0, 0 }; + const char **ptrs_end = ptrs + ranges; + + for (; ptrs < ptrs_end; ++ptrs) { + const char *ptr = *(ptrs++); + const char *end = ptr + *(sizes++); + + for (; ptr < end; ++ptr) { + word.word |= (u8)*ptr << (8*word.bytes); + ++word.bytes; + if (word.bytes == 4) + goto done; + } + } +done: + /* Choose the appropriate encoding */ + switch (word.bytes) { + case 4: + /* 4 byte encoding, but don't match the 1-3 byte encodings */ + if ((word.word >> 8) != 0x808080 && + (word.word >> 16) != 0x8181 && + (word.word >> 24) != 0x82) + break; + /* Fall back to a 3 byte encoding */ + word.bytes = 3; + word.word &= 0x00ffffff; + case 3: + /* 3 byte encoding */ + word.word |= 0x82000000; + break; + case 2: + /* 2 byte encoding */ + word.word |= 0x81810000; + break; + case 1: + /* 1 byte encoding */ + word.word |= 0x80808000; + break; + } + return word; +} + +static unsigned int mips_ejtag_fdc_decode(u32 word, char *buf) +{ + buf[0] = (u8)word; + word >>= 8; + if (word == 0x808080) + return 1; + buf[1] = (u8)word; + word >>= 8; + if (word == 0x8181) + return 2; + buf[2] = (u8)word; + word >>= 8; + if (word == 0x82) + return 3; + buf[3] = (u8)word; + return 4; +} + +/* Console operations */ + +/** + * struct mips_ejtag_fdc_console - Wrapper struct for FDC consoles. + * @cons: Console object. + * @tty_drv: TTY driver associated with this console. + * @lock: Lock to protect concurrent access to other fields. + * This is raw because it may be used very early. + * @initialised: Whether the console is initialised. + * @regs: Registers base address for each CPU. + */ +struct mips_ejtag_fdc_console { + struct console cons; + struct tty_driver *tty_drv; + raw_spinlock_t lock; + bool initialised; + void __iomem *regs[NR_CPUS]; +}; + +/* Low level console write shared by early console and normal console */ +static void mips_ejtag_fdc_console_write(struct console *c, const char *s, + unsigned int count) +{ + struct mips_ejtag_fdc_console *cons = + container_of(c, struct mips_ejtag_fdc_console, cons); + void __iomem *regs; + struct fdc_word word; + unsigned long flags; + unsigned int i, buf_len, cpu; + bool done_cr = false; + char buf[4]; + const char *buf_ptr = buf; + /* Number of bytes of input data encoded up to each byte in buf */ + u8 inc[4]; + + local_irq_save(flags); + cpu = smp_processor_id(); + regs = cons->regs[cpu]; + /* First console output on this CPU? */ + if (!regs) { + regs = mips_cdmm_early_probe(0xfd); + cons->regs[cpu] = regs; + } + /* Already tried and failed to find FDC on this CPU? */ + if (IS_ERR(regs)) + goto out; + while (count) { + /* + * Copy the next few characters to a buffer so we can inject + * carriage returns before newlines. + */ + for (buf_len = 0, i = 0; buf_len < 4 && i < count; ++buf_len) { + if (s[i] == '\n' && !done_cr) { + buf[buf_len] = '\r'; + done_cr = true; + } else { + buf[buf_len] = s[i]; + done_cr = false; + ++i; + } + inc[buf_len] = i; + } + word = mips_ejtag_fdc_encode(&buf_ptr, &buf_len, 1); + count -= inc[word.bytes - 1]; + s += inc[word.bytes - 1]; + + /* Busy wait until there's space in fifo */ + while (ioread32(regs + REG_FDSTAT) & REG_FDSTAT_TXF) + ; + iowrite32(word.word, regs + REG_FDTX(c->index)); + } +out: + local_irq_restore(flags); +} + +static struct tty_driver *mips_ejtag_fdc_console_device(struct console *c, + int *index) +{ + struct mips_ejtag_fdc_console *cons = + container_of(c, struct mips_ejtag_fdc_console, cons); + + *index = c->index; + return cons->tty_drv; +} + +/* Initialise an FDC console (early or normal */ +static int __init mips_ejtag_fdc_console_init(struct mips_ejtag_fdc_console *c) +{ + void __iomem *regs; + unsigned long flags; + int ret = 0; + + raw_spin_lock_irqsave(&c->lock, flags); + /* Don't init twice */ + if (c->initialised) + goto out; + /* Look for the FDC device */ + regs = mips_cdmm_early_probe(0xfd); + if (IS_ERR(regs)) { + ret = PTR_ERR(regs); + goto out; + } + + c->initialised = true; + c->regs[smp_processor_id()] = regs; + register_console(&c->cons); +out: + raw_spin_unlock_irqrestore(&c->lock, flags); + return ret; +} + +static struct mips_ejtag_fdc_console mips_ejtag_fdc_con = { + .cons = { + .name = "fdc", + .write = mips_ejtag_fdc_console_write, + .device = mips_ejtag_fdc_console_device, + .flags = CON_PRINTBUFFER, + .index = -1, + }, + .lock = __RAW_SPIN_LOCK_UNLOCKED(mips_ejtag_fdc_con.lock), +}; + +/* TTY RX/TX operations */ + +/** + * mips_ejtag_fdc_put_chan() - Write out a block of channel data. + * @priv: Pointer to driver private data. + * @chan: Channel number. + * + * Write a single block of data out to the debug adapter. If the circular buffer + * is wrapped then only the first block is written. + * + * Returns: The number of bytes that were written. + */ +static unsigned int mips_ejtag_fdc_put_chan(struct mips_ejtag_fdc_tty *priv, + unsigned int chan) +{ + struct mips_ejtag_fdc_tty_port *dport; + struct tty_struct *tty; + const char *ptrs[2]; + unsigned int sizes[2] = { 0 }; + struct fdc_word word = { .bytes = 0 }; + unsigned long flags; + + dport = &priv->ports[chan]; + spin_lock(&dport->xmit_lock); + if (dport->xmit_cnt) { + ptrs[0] = dport->port.xmit_buf + dport->xmit_tail; + sizes[0] = min_t(unsigned int, + priv->xmit_size - dport->xmit_tail, + dport->xmit_cnt); + ptrs[1] = dport->port.xmit_buf; + sizes[1] = dport->xmit_cnt - sizes[0]; + word = mips_ejtag_fdc_encode(ptrs, sizes, 1 + !!sizes[1]); + + dev_dbg(priv->dev, "%s%u: out %08x: \"%*pE%*pE\"\n", + priv->driver_name, chan, word.word, + min_t(int, word.bytes, sizes[0]), ptrs[0], + max_t(int, 0, word.bytes - sizes[0]), ptrs[1]); + + local_irq_save(flags); + /* Maybe we raced with the console and TX FIFO is full */ + if (mips_ejtag_fdc_read(priv, REG_FDSTAT) & REG_FDSTAT_TXF) + word.bytes = 0; + else + mips_ejtag_fdc_write(priv, REG_FDTX(chan), word.word); + local_irq_restore(flags); + + dport->xmit_cnt -= word.bytes; + if (!dport->xmit_cnt) { + /* Reset pointers to avoid wraps */ + dport->xmit_head = 0; + dport->xmit_tail = 0; + complete(&dport->xmit_empty); + } else { + dport->xmit_tail += word.bytes; + if (dport->xmit_tail >= priv->xmit_size) + dport->xmit_tail -= priv->xmit_size; + } + atomic_sub(word.bytes, &priv->xmit_total); + } + spin_unlock(&dport->xmit_lock); + + /* If we've made more data available, wake up tty */ + if (sizes[0] && word.bytes) { + tty = tty_port_tty_get(&dport->port); + if (tty) { + tty_wakeup(tty); + tty_kref_put(tty); + } + } + + return word.bytes; +} + +/** + * mips_ejtag_fdc_put() - Kernel thread to write out channel data to FDC. + * @arg: Driver pointer. + * + * This kernel thread runs while @priv->xmit_total != 0, and round robins the + * channels writing out blocks of buffered data to the FDC TX FIFO. + */ +static int mips_ejtag_fdc_put(void *arg) +{ + struct mips_ejtag_fdc_tty *priv = arg; + struct mips_ejtag_fdc_tty_port *dport; + unsigned int ret; + u32 cfg; + + __set_current_state(TASK_RUNNING); + while (!kthread_should_stop()) { + /* Wait for data to actually write */ + wait_event_interruptible(priv->waitqueue, + atomic_read(&priv->xmit_total) || + kthread_should_stop()); + if (kthread_should_stop()) + break; + + /* Wait for TX FIFO space to write data */ + raw_spin_lock_irq(&priv->lock); + if (mips_ejtag_fdc_read(priv, REG_FDSTAT) & REG_FDSTAT_TXF) { + priv->xmit_full = true; + if (priv->irq >= 0) { + /* Enable TX interrupt */ + cfg = mips_ejtag_fdc_read(priv, REG_FDCFG); + cfg &= ~REG_FDCFG_TXINTTHRES; + cfg |= REG_FDCFG_TXINTTHRES_NOTFULL; + mips_ejtag_fdc_write(priv, REG_FDCFG, cfg); + } + } + raw_spin_unlock_irq(&priv->lock); + wait_event_interruptible(priv->waitqueue, + !(mips_ejtag_fdc_read(priv, REG_FDSTAT) + & REG_FDSTAT_TXF) || + kthread_should_stop()); + if (kthread_should_stop()) + break; + + /* Find next channel with data to output */ + for (;;) { + dport = &priv->ports[priv->xmit_next]; + spin_lock(&dport->xmit_lock); + ret = dport->xmit_cnt; + spin_unlock(&dport->xmit_lock); + if (ret) + break; + /* Round robin */ + ++priv->xmit_next; + if (priv->xmit_next >= NUM_TTY_CHANNELS) + priv->xmit_next = 0; + } + + /* Try writing data to the chosen channel */ + ret = mips_ejtag_fdc_put_chan(priv, priv->xmit_next); + + /* + * If anything was output, move on to the next channel so as not + * to starve other channels. + */ + if (ret) { + ++priv->xmit_next; + if (priv->xmit_next >= NUM_TTY_CHANNELS) + priv->xmit_next = 0; + } + } + + return 0; +} + +/** + * mips_ejtag_fdc_handle() - Handle FDC events. + * @priv: Pointer to driver private data. + * + * Handle FDC events, such as new incoming data which needs draining out of the + * RX FIFO and feeding into the appropriate TTY ports, and space becoming + * available in the TX FIFO which would allow more data to be written out. + */ +static void mips_ejtag_fdc_handle(struct mips_ejtag_fdc_tty *priv) +{ + struct mips_ejtag_fdc_tty_port *dport; + unsigned int stat, channel, data, cfg, i, flipped; + int len; + char buf[4]; + + for (;;) { + /* Find which channel the next FDC word is destined for */ + stat = mips_ejtag_fdc_read(priv, REG_FDSTAT); + if (stat & REG_FDSTAT_RXE) + break; + channel = (stat & REG_FDSTAT_RXCHAN) >> REG_FDSTAT_RXCHAN_SHIFT; + dport = &priv->ports[channel]; + + /* Read out the FDC word, decode it, and pass to tty layer */ + raw_spin_lock(&dport->rx_lock); + data = mips_ejtag_fdc_read(priv, REG_FDRX); + + len = mips_ejtag_fdc_decode(data, buf); + dev_dbg(priv->dev, "%s%u: in %08x: \"%*pE\"\n", + priv->driver_name, channel, data, len, buf); + + flipped = 0; + for (i = 0; i < len; ++i) { +#ifdef CONFIG_MAGIC_SYSRQ +#ifdef CONFIG_MIPS_EJTAG_FDC_KGDB + /* Support just Ctrl+C with KGDB channel */ + if (channel == CONFIG_MIPS_EJTAG_FDC_KGDB_CHAN) { + if (buf[i] == '\x03') { /* ^C */ + handle_sysrq('g'); + continue; + } + } +#endif + /* Support Ctrl+O for console channel */ + if (channel == mips_ejtag_fdc_con.cons.index) { + if (buf[i] == '\x0f') { /* ^O */ + priv->sysrq_pressed = + !priv->sysrq_pressed; + if (priv->sysrq_pressed) + continue; + } else if (priv->sysrq_pressed) { + handle_sysrq(buf[i]); + priv->sysrq_pressed = false; + continue; + } + } +#endif /* CONFIG_MAGIC_SYSRQ */ + + /* Check the port isn't being shut down */ + if (!dport->rx_buf) + continue; + + flipped += tty_insert_flip_char(&dport->port, buf[i], + TTY_NORMAL); + } + if (flipped) + tty_flip_buffer_push(&dport->port); + + raw_spin_unlock(&dport->rx_lock); + } + + /* If TX FIFO no longer full we may be able to write more data */ + raw_spin_lock(&priv->lock); + if (priv->xmit_full && !(stat & REG_FDSTAT_TXF)) { + priv->xmit_full = false; + + /* Disable TX interrupt */ + cfg = mips_ejtag_fdc_read(priv, REG_FDCFG); + cfg &= ~REG_FDCFG_TXINTTHRES; + cfg |= REG_FDCFG_TXINTTHRES_DISABLED; + mips_ejtag_fdc_write(priv, REG_FDCFG, cfg); + + /* Wait the kthread so it can try writing more data */ + wake_up_interruptible(&priv->waitqueue); + } + raw_spin_unlock(&priv->lock); +} + +/** + * mips_ejtag_fdc_isr() - Interrupt handler. + * @irq: IRQ number. + * @dev_id: Pointer to driver private data. + * + * This is the interrupt handler, used when interrupts are enabled. + * + * It simply triggers the common FDC handler code. + * + * Returns: IRQ_HANDLED if an FDC interrupt was pending. + * IRQ_NONE otherwise. + */ +static irqreturn_t mips_ejtag_fdc_isr(int irq, void *dev_id) +{ + struct mips_ejtag_fdc_tty *priv = dev_id; + + /* + * We're not using proper per-cpu IRQs, so we must be careful not to + * handle IRQs on CPUs we're not interested in. + * + * Ideally proper per-cpu IRQ handlers could be used, but that doesn't + * fit well with the whole sharing of the main CPU IRQ lines. When we + * have something with a GIC that routes the FDC IRQs (i.e. no sharing + * between handlers) then support could be added more easily. + */ + if (smp_processor_id() != priv->cpu) + return IRQ_NONE; + + /* If no FDC interrupt pending, it wasn't for us */ + if (!(read_c0_cause() & CAUSEF_FDCI)) + return IRQ_NONE; + + mips_ejtag_fdc_handle(priv); + return IRQ_HANDLED; +} + +/** + * mips_ejtag_fdc_tty_timer() - Poll FDC for incoming data. + * @opaque: Pointer to driver private data. + * + * This is the timer handler for when interrupts are disabled and polling the + * FDC state is required. + * + * It simply triggers the common FDC handler code and arranges for further + * polling. + */ +static void mips_ejtag_fdc_tty_timer(unsigned long opaque) +{ + struct mips_ejtag_fdc_tty *priv = (void *)opaque; + + mips_ejtag_fdc_handle(priv); + if (!priv->removing) + mod_timer_pinned(&priv->poll_timer, jiffies + FDC_TTY_POLL); +} + +/* TTY Port operations */ + +static int mips_ejtag_fdc_tty_port_activate(struct tty_port *port, + struct tty_struct *tty) +{ + struct mips_ejtag_fdc_tty_port *dport = + container_of(port, struct mips_ejtag_fdc_tty_port, port); + void *rx_buf; + + /* Allocate the buffer we use for writing data */ + if (tty_port_alloc_xmit_buf(port) < 0) + goto err; + + /* Allocate the buffer we use for reading data */ + rx_buf = kzalloc(RX_BUF_SIZE, GFP_KERNEL); + if (!rx_buf) + goto err_free_xmit; + + raw_spin_lock_irq(&dport->rx_lock); + dport->rx_buf = rx_buf; + raw_spin_unlock_irq(&dport->rx_lock); + + return 0; +err_free_xmit: + tty_port_free_xmit_buf(port); +err: + return -ENOMEM; +} + +static void mips_ejtag_fdc_tty_port_shutdown(struct tty_port *port) +{ + struct mips_ejtag_fdc_tty_port *dport = + container_of(port, struct mips_ejtag_fdc_tty_port, port); + struct mips_ejtag_fdc_tty *priv = dport->driver; + void *rx_buf; + unsigned int count; + + spin_lock(&dport->xmit_lock); + count = dport->xmit_cnt; + spin_unlock(&dport->xmit_lock); + if (count) { + /* + * There's still data to write out, so wake and wait for the + * writer thread to drain the buffer. + */ + wake_up_interruptible(&priv->waitqueue); + wait_for_completion(&dport->xmit_empty); + } + + /* Null the read buffer (timer could still be running!) */ + raw_spin_lock_irq(&dport->rx_lock); + rx_buf = dport->rx_buf; + dport->rx_buf = NULL; + raw_spin_unlock_irq(&dport->rx_lock); + /* Free the read buffer */ + kfree(rx_buf); + + /* Free the write buffer */ + tty_port_free_xmit_buf(port); +} + +static const struct tty_port_operations mips_ejtag_fdc_tty_port_ops = { + .activate = mips_ejtag_fdc_tty_port_activate, + .shutdown = mips_ejtag_fdc_tty_port_shutdown, +}; + +/* TTY operations */ + +static int mips_ejtag_fdc_tty_install(struct tty_driver *driver, + struct tty_struct *tty) +{ + struct mips_ejtag_fdc_tty *priv = driver->driver_state; + + tty->driver_data = &priv->ports[tty->index]; + return tty_port_install(&priv->ports[tty->index].port, driver, tty); +} + +static int mips_ejtag_fdc_tty_open(struct tty_struct *tty, struct file *filp) +{ + return tty_port_open(tty->port, tty, filp); +} + +static void mips_ejtag_fdc_tty_close(struct tty_struct *tty, struct file *filp) +{ + return tty_port_close(tty->port, tty, filp); +} + +static void mips_ejtag_fdc_tty_hangup(struct tty_struct *tty) +{ + struct mips_ejtag_fdc_tty_port *dport = tty->driver_data; + struct mips_ejtag_fdc_tty *priv = dport->driver; + + /* Drop any data in the xmit buffer */ + spin_lock(&dport->xmit_lock); + if (dport->xmit_cnt) { + atomic_sub(dport->xmit_cnt, &priv->xmit_total); + dport->xmit_cnt = 0; + dport->xmit_head = 0; + dport->xmit_tail = 0; + complete(&dport->xmit_empty); + } + spin_unlock(&dport->xmit_lock); + + tty_port_hangup(tty->port); +} + +static int mips_ejtag_fdc_tty_write(struct tty_struct *tty, + const unsigned char *buf, int total) +{ + int count, block; + struct mips_ejtag_fdc_tty_port *dport = tty->driver_data; + struct mips_ejtag_fdc_tty *priv = dport->driver; + + /* + * Write to output buffer. + * + * The reason that we asynchronously write the buffer is because if we + * were to write the buffer synchronously then because the channels are + * per-CPU the buffer would be written to the channel of whatever CPU + * we're running on. + * + * What we actually want to happen is have all input and output done on + * one CPU. + */ + spin_lock(&dport->xmit_lock); + /* Work out how many bytes we can write to the xmit buffer */ + total = min(total, (int)(priv->xmit_size - dport->xmit_cnt)); + atomic_add(total, &priv->xmit_total); + dport->xmit_cnt += total; + /* Write the actual bytes (may need splitting if it wraps) */ + for (count = total; count; count -= block) { + block = min(count, (int)(priv->xmit_size - dport->xmit_head)); + memcpy(dport->port.xmit_buf + dport->xmit_head, buf, block); + dport->xmit_head += block; + if (dport->xmit_head >= priv->xmit_size) + dport->xmit_head -= priv->xmit_size; + buf += block; + } + count = dport->xmit_cnt; + /* Xmit buffer no longer empty? */ + if (count) + reinit_completion(&dport->xmit_empty); + spin_unlock(&dport->xmit_lock); + + /* Wake up the kthread */ + if (total) + wake_up_interruptible(&priv->waitqueue); + return total; +} + +static int mips_ejtag_fdc_tty_write_room(struct tty_struct *tty) +{ + struct mips_ejtag_fdc_tty_port *dport = tty->driver_data; + struct mips_ejtag_fdc_tty *priv = dport->driver; + int room; + + /* Report the space in the xmit buffer */ + spin_lock(&dport->xmit_lock); + room = priv->xmit_size - dport->xmit_cnt; + spin_unlock(&dport->xmit_lock); + + return room; +} + +static int mips_ejtag_fdc_tty_chars_in_buffer(struct tty_struct *tty) +{ + struct mips_ejtag_fdc_tty_port *dport = tty->driver_data; + int chars; + + /* Report the number of bytes in the xmit buffer */ + spin_lock(&dport->xmit_lock); + chars = dport->xmit_cnt; + spin_unlock(&dport->xmit_lock); + + return chars; +} + +static const struct tty_operations mips_ejtag_fdc_tty_ops = { + .install = mips_ejtag_fdc_tty_install, + .open = mips_ejtag_fdc_tty_open, + .close = mips_ejtag_fdc_tty_close, + .hangup = mips_ejtag_fdc_tty_hangup, + .write = mips_ejtag_fdc_tty_write, + .write_room = mips_ejtag_fdc_tty_write_room, + .chars_in_buffer = mips_ejtag_fdc_tty_chars_in_buffer, +}; + +static int mips_ejtag_fdc_tty_probe(struct mips_cdmm_device *dev) +{ + int ret, nport; + struct mips_ejtag_fdc_tty_port *dport; + struct mips_ejtag_fdc_tty *priv; + struct tty_driver *driver; + unsigned int cfg, tx_fifo; + + priv = devm_kzalloc(&dev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + priv->cpu = dev->cpu; + priv->dev = &dev->dev; + mips_cdmm_set_drvdata(dev, priv); + atomic_set(&priv->xmit_total, 0); + raw_spin_lock_init(&priv->lock); + + priv->reg = devm_ioremap_nocache(priv->dev, dev->res.start, + resource_size(&dev->res)); + if (!priv->reg) { + dev_err(priv->dev, "ioremap failed for resource %pR\n", + &dev->res); + return -ENOMEM; + } + + cfg = mips_ejtag_fdc_read(priv, REG_FDCFG); + tx_fifo = (cfg & REG_FDCFG_TXFIFOSIZE) >> REG_FDCFG_TXFIFOSIZE_SHIFT; + /* Disable interrupts */ + cfg &= ~(REG_FDCFG_TXINTTHRES | REG_FDCFG_RXINTTHRES); + cfg |= REG_FDCFG_TXINTTHRES_DISABLED; + cfg |= REG_FDCFG_RXINTTHRES_DISABLED; + mips_ejtag_fdc_write(priv, REG_FDCFG, cfg); + + /* Make each port's xmit FIFO big enough to fill FDC TX FIFO */ + priv->xmit_size = min(tx_fifo * 4, (unsigned int)SERIAL_XMIT_SIZE); + + driver = tty_alloc_driver(NUM_TTY_CHANNELS, TTY_DRIVER_REAL_RAW); + if (IS_ERR(driver)) + return PTR_ERR(driver); + priv->driver = driver; + + driver->driver_name = "ejtag_fdc"; + snprintf(priv->fdc_name, sizeof(priv->fdc_name), "ttyFDC%u", dev->cpu); + snprintf(priv->driver_name, sizeof(priv->driver_name), "%sc", + priv->fdc_name); + driver->name = priv->driver_name; + driver->major = 0; /* Auto-allocate */ + driver->minor_start = 0; + driver->type = TTY_DRIVER_TYPE_SERIAL; + driver->subtype = SERIAL_TYPE_NORMAL; + driver->init_termios = tty_std_termios; + driver->init_termios.c_cflag |= CLOCAL; + driver->driver_state = priv; + + tty_set_operations(driver, &mips_ejtag_fdc_tty_ops); + for (nport = 0; nport < NUM_TTY_CHANNELS; nport++) { + dport = &priv->ports[nport]; + dport->driver = priv; + tty_port_init(&dport->port); + dport->port.ops = &mips_ejtag_fdc_tty_port_ops; + raw_spin_lock_init(&dport->rx_lock); + spin_lock_init(&dport->xmit_lock); + /* The xmit buffer starts empty, i.e. completely written */ + init_completion(&dport->xmit_empty); + complete(&dport->xmit_empty); + } + + /* Set up the console */ + mips_ejtag_fdc_con.regs[dev->cpu] = priv->reg; + if (dev->cpu == 0) + mips_ejtag_fdc_con.tty_drv = driver; + + init_waitqueue_head(&priv->waitqueue); + priv->thread = kthread_create(mips_ejtag_fdc_put, priv, priv->fdc_name); + if (IS_ERR(priv->thread)) { + ret = PTR_ERR(priv->thread); + dev_err(priv->dev, "Couldn't create kthread (%d)\n", ret); + goto err_destroy_ports; + } + /* + * Bind the writer thread to the right CPU so it can't migrate. + * The channels are per-CPU and we want all channel I/O to be on a + * single predictable CPU. + */ + kthread_bind(priv->thread, dev->cpu); + wake_up_process(priv->thread); + + /* Look for an FDC IRQ */ + priv->irq = -1; + if (get_c0_fdc_int) + priv->irq = get_c0_fdc_int(); + + /* Try requesting the IRQ */ + if (priv->irq >= 0) { + /* + * IRQF_SHARED, IRQF_NO_SUSPEND: The FDC IRQ may be shared with + * other local interrupts such as the timer which sets + * IRQF_TIMER (including IRQF_NO_SUSPEND). + * + * IRQF_NO_THREAD: The FDC IRQ isn't individually maskable so it + * cannot be deferred and handled by a thread on RT kernels. For + * this reason any spinlocks used from the ISR are raw. + */ + ret = devm_request_irq(priv->dev, priv->irq, mips_ejtag_fdc_isr, + IRQF_PERCPU | IRQF_SHARED | + IRQF_NO_THREAD | IRQF_NO_SUSPEND, + priv->fdc_name, priv); + if (ret) + priv->irq = -1; + } + if (priv->irq >= 0) { + /* IRQ is usable, enable RX interrupt */ + raw_spin_lock_irq(&priv->lock); + cfg = mips_ejtag_fdc_read(priv, REG_FDCFG); + cfg &= ~REG_FDCFG_RXINTTHRES; + cfg |= REG_FDCFG_RXINTTHRES_NOTEMPTY; + mips_ejtag_fdc_write(priv, REG_FDCFG, cfg); + raw_spin_unlock_irq(&priv->lock); + } else { + /* If we didn't get an usable IRQ, poll instead */ + setup_timer(&priv->poll_timer, mips_ejtag_fdc_tty_timer, + (unsigned long)priv); + priv->poll_timer.expires = jiffies + FDC_TTY_POLL; + /* + * Always attach the timer to the right CPU. The channels are + * per-CPU so all polling should be from a single CPU. + */ + add_timer_on(&priv->poll_timer, dev->cpu); + + dev_info(priv->dev, "No usable IRQ, polling enabled\n"); + } + + ret = tty_register_driver(driver); + if (ret < 0) { + dev_err(priv->dev, "Couldn't install tty driver (%d)\n", ret); + goto err_stop_irq; + } + + return 0; + +err_stop_irq: + if (priv->irq >= 0) { + raw_spin_lock_irq(&priv->lock); + cfg = mips_ejtag_fdc_read(priv, REG_FDCFG); + /* Disable interrupts */ + cfg &= ~(REG_FDCFG_TXINTTHRES | REG_FDCFG_RXINTTHRES); + cfg |= REG_FDCFG_TXINTTHRES_DISABLED; + cfg |= REG_FDCFG_RXINTTHRES_DISABLED; + mips_ejtag_fdc_write(priv, REG_FDCFG, cfg); + raw_spin_unlock_irq(&priv->lock); + } else { + priv->removing = true; + del_timer_sync(&priv->poll_timer); + } + kthread_stop(priv->thread); +err_destroy_ports: + if (dev->cpu == 0) + mips_ejtag_fdc_con.tty_drv = NULL; + for (nport = 0; nport < NUM_TTY_CHANNELS; nport++) { + dport = &priv->ports[nport]; + tty_port_destroy(&dport->port); + } + put_tty_driver(priv->driver); + return ret; +} + +static int mips_ejtag_fdc_tty_remove(struct mips_cdmm_device *dev) +{ + struct mips_ejtag_fdc_tty *priv = mips_cdmm_get_drvdata(dev); + struct mips_ejtag_fdc_tty_port *dport; + int nport; + unsigned int cfg; + + if (priv->irq >= 0) { + raw_spin_lock_irq(&priv->lock); + cfg = mips_ejtag_fdc_read(priv, REG_FDCFG); + /* Disable interrupts */ + cfg &= ~(REG_FDCFG_TXINTTHRES | REG_FDCFG_RXINTTHRES); + cfg |= REG_FDCFG_TXINTTHRES_DISABLED; + cfg |= REG_FDCFG_RXINTTHRES_DISABLED; + mips_ejtag_fdc_write(priv, REG_FDCFG, cfg); + raw_spin_unlock_irq(&priv->lock); + } else { + priv->removing = true; + del_timer_sync(&priv->poll_timer); + } + kthread_stop(priv->thread); + if (dev->cpu == 0) + mips_ejtag_fdc_con.tty_drv = NULL; + tty_unregister_driver(priv->driver); + for (nport = 0; nport < NUM_TTY_CHANNELS; nport++) { + dport = &priv->ports[nport]; + tty_port_destroy(&dport->port); + } + put_tty_driver(priv->driver); + return 0; +} + +static int mips_ejtag_fdc_tty_cpu_down(struct mips_cdmm_device *dev) +{ + struct mips_ejtag_fdc_tty *priv = mips_cdmm_get_drvdata(dev); + unsigned int cfg; + + if (priv->irq >= 0) { + raw_spin_lock_irq(&priv->lock); + cfg = mips_ejtag_fdc_read(priv, REG_FDCFG); + /* Disable interrupts */ + cfg &= ~(REG_FDCFG_TXINTTHRES | REG_FDCFG_RXINTTHRES); + cfg |= REG_FDCFG_TXINTTHRES_DISABLED; + cfg |= REG_FDCFG_RXINTTHRES_DISABLED; + mips_ejtag_fdc_write(priv, REG_FDCFG, cfg); + raw_spin_unlock_irq(&priv->lock); + } else { + priv->removing = true; + del_timer_sync(&priv->poll_timer); + } + kthread_stop(priv->thread); + + return 0; +} + +static int mips_ejtag_fdc_tty_cpu_up(struct mips_cdmm_device *dev) +{ + struct mips_ejtag_fdc_tty *priv = mips_cdmm_get_drvdata(dev); + unsigned int cfg; + int ret = 0; + + if (priv->irq >= 0) { + /* + * IRQ is usable, enable RX interrupt + * This must be before kthread is restarted, as kthread may + * enable TX interrupt. + */ + raw_spin_lock_irq(&priv->lock); + cfg = mips_ejtag_fdc_read(priv, REG_FDCFG); + cfg &= ~(REG_FDCFG_TXINTTHRES | REG_FDCFG_RXINTTHRES); + cfg |= REG_FDCFG_TXINTTHRES_DISABLED; + cfg |= REG_FDCFG_RXINTTHRES_NOTEMPTY; + mips_ejtag_fdc_write(priv, REG_FDCFG, cfg); + raw_spin_unlock_irq(&priv->lock); + } else { + /* Restart poll timer */ + priv->removing = false; + add_timer_on(&priv->poll_timer, dev->cpu); + } + + /* Restart the kthread */ + priv->thread = kthread_create(mips_ejtag_fdc_put, priv, priv->fdc_name); + if (IS_ERR(priv->thread)) { + ret = PTR_ERR(priv->thread); + dev_err(priv->dev, "Couldn't re-create kthread (%d)\n", ret); + goto out; + } + /* Bind it back to the right CPU and set it off */ + kthread_bind(priv->thread, dev->cpu); + wake_up_process(priv->thread); +out: + return ret; +} + +static struct mips_cdmm_device_id mips_ejtag_fdc_tty_ids[] = { + { .type = 0xfd }, + { } +}; + +static struct mips_cdmm_driver mips_ejtag_fdc_tty_driver = { + .drv = { + .name = "mips_ejtag_fdc", + }, + .probe = mips_ejtag_fdc_tty_probe, + .remove = mips_ejtag_fdc_tty_remove, + .cpu_down = mips_ejtag_fdc_tty_cpu_down, + .cpu_up = mips_ejtag_fdc_tty_cpu_up, + .id_table = mips_ejtag_fdc_tty_ids, +}; +module_mips_cdmm_driver(mips_ejtag_fdc_tty_driver); + +static int __init mips_ejtag_fdc_init_console(void) +{ + return mips_ejtag_fdc_console_init(&mips_ejtag_fdc_con); +} +console_initcall(mips_ejtag_fdc_init_console); + +#ifdef CONFIG_MIPS_EJTAG_FDC_EARLYCON +static struct mips_ejtag_fdc_console mips_ejtag_fdc_earlycon = { + .cons = { + .name = "early_fdc", + .write = mips_ejtag_fdc_console_write, + .flags = CON_PRINTBUFFER | CON_BOOT, + .index = CONSOLE_CHANNEL, + }, + .lock = __RAW_SPIN_LOCK_UNLOCKED(mips_ejtag_fdc_earlycon.lock), +}; + +int __init setup_early_fdc_console(void) +{ + return mips_ejtag_fdc_console_init(&mips_ejtag_fdc_earlycon); +} +#endif + +#ifdef CONFIG_MIPS_EJTAG_FDC_KGDB + +/* read buffer to allow decompaction */ +static unsigned int kgdbfdc_rbuflen; +static unsigned int kgdbfdc_rpos; +static char kgdbfdc_rbuf[4]; + +/* write buffer to allow compaction */ +static unsigned int kgdbfdc_wbuflen; +static char kgdbfdc_wbuf[4]; + +static void __iomem *kgdbfdc_setup(void) +{ + void __iomem *regs; + unsigned int cpu; + + /* Find address, piggy backing off console percpu regs */ + cpu = smp_processor_id(); + regs = mips_ejtag_fdc_con.regs[cpu]; + /* First console output on this CPU? */ + if (!regs) { + regs = mips_cdmm_early_probe(0xfd); + mips_ejtag_fdc_con.regs[cpu] = regs; + } + /* Already tried and failed to find FDC on this CPU? */ + if (IS_ERR(regs)) + return regs; + + return regs; +} + +/* read a character from the read buffer, filling from FDC RX FIFO */ +static int kgdbfdc_read_char(void) +{ + unsigned int stat, channel, data; + void __iomem *regs; + + /* No more data, try and read another FDC word from RX FIFO */ + if (kgdbfdc_rpos >= kgdbfdc_rbuflen) { + kgdbfdc_rpos = 0; + kgdbfdc_rbuflen = 0; + + regs = kgdbfdc_setup(); + if (IS_ERR(regs)) + return NO_POLL_CHAR; + + /* Read next word from KGDB channel */ + do { + stat = ioread32(regs + REG_FDSTAT); + + /* No data waiting? */ + if (stat & REG_FDSTAT_RXE) + return NO_POLL_CHAR; + + /* Read next word */ + channel = (stat & REG_FDSTAT_RXCHAN) >> + REG_FDSTAT_RXCHAN_SHIFT; + data = ioread32(regs + REG_FDRX); + } while (channel != CONFIG_MIPS_EJTAG_FDC_KGDB_CHAN); + + /* Decode into rbuf */ + kgdbfdc_rbuflen = mips_ejtag_fdc_decode(data, kgdbfdc_rbuf); + } + pr_devel("kgdbfdc r %c\n", kgdbfdc_rbuf[kgdbfdc_rpos]); + return kgdbfdc_rbuf[kgdbfdc_rpos++]; +} + +/* push an FDC word from write buffer to TX FIFO */ +static void kgdbfdc_push_one(void) +{ + const char *bufs[1] = { kgdbfdc_wbuf }; + struct fdc_word word; + void __iomem *regs; + unsigned int i; + + /* Construct a word from any data in buffer */ + word = mips_ejtag_fdc_encode(bufs, &kgdbfdc_wbuflen, 1); + /* Relocate any remaining data to beginnning of buffer */ + kgdbfdc_wbuflen -= word.bytes; + for (i = 0; i < kgdbfdc_wbuflen; ++i) + kgdbfdc_wbuf[i] = kgdbfdc_wbuf[i + word.bytes]; + + regs = kgdbfdc_setup(); + if (IS_ERR(regs)) + return; + + /* Busy wait until there's space in fifo */ + while (ioread32(regs + REG_FDSTAT) & REG_FDSTAT_TXF) + ; + iowrite32(word.word, regs + REG_FDTX(CONFIG_MIPS_EJTAG_FDC_KGDB_CHAN)); +} + +/* flush the whole write buffer to the TX FIFO */ +static void kgdbfdc_flush(void) +{ + while (kgdbfdc_wbuflen) + kgdbfdc_push_one(); +} + +/* write a character into the write buffer, writing out if full */ +static void kgdbfdc_write_char(u8 chr) +{ + pr_devel("kgdbfdc w %c\n", chr); + kgdbfdc_wbuf[kgdbfdc_wbuflen++] = chr; + if (kgdbfdc_wbuflen >= sizeof(kgdbfdc_wbuf)) + kgdbfdc_push_one(); +} + +static struct kgdb_io kgdbfdc_io_ops = { + .name = "kgdbfdc", + .read_char = kgdbfdc_read_char, + .write_char = kgdbfdc_write_char, + .flush = kgdbfdc_flush, +}; + +static int __init kgdbfdc_init(void) +{ + kgdb_register_io_module(&kgdbfdc_io_ops); + return 0; +} +early_initcall(kgdbfdc_init); +#endif diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index e3b9570a1eff..deae122c9c4b 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -2138,8 +2138,8 @@ int serial8250_do_startup(struct uart_port *port) /* * Clear the interrupt registers. */ - if (serial_port_in(port, UART_LSR) & UART_LSR_DR) - serial_port_in(port, UART_RX); + serial_port_in(port, UART_LSR); + serial_port_in(port, UART_RX); serial_port_in(port, UART_IIR); serial_port_in(port, UART_MSR); @@ -2300,8 +2300,8 @@ dont_test_tx_en: * saved flags to avoid getting false values from polling * routines or the previous session. */ - if (serial_port_in(port, UART_LSR) & UART_LSR_DR) - serial_port_in(port, UART_RX); + serial_port_in(port, UART_LSR); + serial_port_in(port, UART_RX); serial_port_in(port, UART_IIR); serial_port_in(port, UART_MSR); up->lsr_saved_flags = 0; @@ -2394,8 +2394,7 @@ void serial8250_do_shutdown(struct uart_port *port) * Read data port to reset things, and then unlink from * the IRQ chain. */ - if (serial_port_in(port, UART_LSR) & UART_LSR_DR) - serial_port_in(port, UART_RX); + serial_port_in(port, UART_RX); serial8250_rpm_put(up); del_timer_sync(&up->timer); diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c index e60116235836..6ae5b8560e4d 100644 --- a/drivers/tty/serial/8250/8250_dw.c +++ b/drivers/tty/serial/8250/8250_dw.c @@ -59,6 +59,8 @@ struct dw8250_data { u8 usr_reg; int last_mcr; int line; + int msr_mask_on; + int msr_mask_off; struct clk *clk; struct clk *pclk; struct reset_control *rst; @@ -81,6 +83,12 @@ static inline int dw8250_modify_msr(struct uart_port *p, int offset, int value) value &= ~UART_MSR_DCTS; } + /* Override any modem control signals if needed */ + if (offset == UART_MSR) { + value |= d->msr_mask_on; + value &= ~d->msr_mask_off; + } + return value; } @@ -111,7 +119,10 @@ static void dw8250_serial_out(struct uart_port *p, int offset, int value) dw8250_force_idle(p); writeb(value, p->membase + (UART_LCR << p->regshift)); } - dev_err(p->dev, "Couldn't set LCR to %d\n", value); + /* + * FIXME: this deadlocks if port->lock is already held + * dev_err(p->dev, "Couldn't set LCR to %d\n", value); + */ } } @@ -155,7 +166,10 @@ static void dw8250_serial_outq(struct uart_port *p, int offset, int value) __raw_writeq(value & 0xff, p->membase + (UART_LCR << p->regshift)); } - dev_err(p->dev, "Couldn't set LCR to %d\n", value); + /* + * FIXME: this deadlocks if port->lock is already held + * dev_err(p->dev, "Couldn't set LCR to %d\n", value); + */ } } #endif /* CONFIG_64BIT */ @@ -179,7 +193,10 @@ static void dw8250_serial_out32(struct uart_port *p, int offset, int value) dw8250_force_idle(p); writel(value, p->membase + (UART_LCR << p->regshift)); } - dev_err(p->dev, "Couldn't set LCR to %d\n", value); + /* + * FIXME: this deadlocks if port->lock is already held + * dev_err(p->dev, "Couldn't set LCR to %d\n", value); + */ } } @@ -334,6 +351,30 @@ static int dw8250_probe_of(struct uart_port *p, if (id >= 0) p->line = id; + if (of_property_read_bool(np, "dcd-override")) { + /* Always report DCD as active */ + data->msr_mask_on |= UART_MSR_DCD; + data->msr_mask_off |= UART_MSR_DDCD; + } + + if (of_property_read_bool(np, "dsr-override")) { + /* Always report DSR as active */ + data->msr_mask_on |= UART_MSR_DSR; + data->msr_mask_off |= UART_MSR_DDSR; + } + + if (of_property_read_bool(np, "cts-override")) { + /* Always report DSR as active */ + data->msr_mask_on |= UART_MSR_DSR; + data->msr_mask_off |= UART_MSR_DDSR; + } + + if (of_property_read_bool(np, "ri-override")) { + /* Always report Ring indicator as inactive */ + data->msr_mask_off |= UART_MSR_RI; + data->msr_mask_off |= UART_MSR_TERI; + } + /* clock got configured through clk api, all done */ if (p->uartclk) return 0; diff --git a/drivers/tty/serial/8250/8250_fintek.c b/drivers/tty/serial/8250/8250_fintek.c index 1e6899bc9429..5815e81b5fc6 100644 --- a/drivers/tty/serial/8250/8250_fintek.c +++ b/drivers/tty/serial/8250/8250_fintek.c @@ -234,18 +234,7 @@ static struct pnp_driver fintek_8250_driver = { .id_table = fintek_dev_table, }; -static int fintek_8250_init(void) -{ - return pnp_register_driver(&fintek_8250_driver); -} -module_init(fintek_8250_init); - -static void fintek_8250_exit(void) -{ - pnp_unregister_driver(&fintek_8250_driver); -} -module_exit(fintek_8250_exit); - +module_pnp_driver(fintek_8250_driver); MODULE_DESCRIPTION("Fintek F812164 module"); MODULE_AUTHOR("Ricardo Ribalda <ricardo.ribalda@gmail.com>"); MODULE_LICENSE("GPL"); diff --git a/drivers/tty/serial/8250/8250_pci.c b/drivers/tty/serial/8250/8250_pci.c index daf2c82984e9..892eb32cdef4 100644 --- a/drivers/tty/serial/8250/8250_pci.c +++ b/drivers/tty/serial/8250/8250_pci.c @@ -69,7 +69,7 @@ static void moan_device(const char *str, struct pci_dev *dev) "Please send the output of lspci -vv, this\n" "message (0x%04x,0x%04x,0x%04x,0x%04x), the\n" "manufacturer and name of serial board or\n" - "modem board to rmk+serial@arm.linux.org.uk.\n", + "modem board to <linux-serial@vger.kernel.org>.\n", pci_name(dev), str, dev->vendor, dev->device, dev->subsystem_vendor, dev->subsystem_device); } @@ -1989,13 +1989,6 @@ static struct pci_serial_quirk pci_serial_quirks[] __refdata = { }, { .vendor = PCI_VENDOR_ID_INTEL, - .device = PCI_DEVICE_ID_INTEL_QRK_UART, - .subvendor = PCI_ANY_ID, - .subdevice = PCI_ANY_ID, - .setup = pci_default_setup, - }, - { - .vendor = PCI_VENDOR_ID_INTEL, .device = PCI_DEVICE_ID_INTEL_BSW_UART1, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, @@ -2201,13 +2194,6 @@ static struct pci_serial_quirk pci_serial_quirks[] __refdata = { */ { .vendor = PCI_VENDOR_ID_PLX, - .device = PCI_DEVICE_ID_PLX_9030, - .subvendor = PCI_SUBVENDOR_ID_PERLE, - .subdevice = PCI_ANY_ID, - .setup = pci_default_setup, - }, - { - .vendor = PCI_VENDOR_ID_PLX, .device = PCI_DEVICE_ID_PLX_9050, .subvendor = PCI_SUBVENDOR_ID_EXSYS, .subdevice = PCI_SUBDEVICE_ID_EXSYS_4055, @@ -5415,10 +5401,6 @@ static struct pci_device_id serial_pci_tbl[] = { PCI_ANY_ID, PCI_ANY_ID, 0, 0, pbn_b0_bt_2_115200 }, - { PCI_VENDOR_ID_WCH, PCI_DEVICE_ID_WCH_CH352_2S, - PCI_ANY_ID, PCI_ANY_ID, - 0, 0, pbn_b0_bt_2_115200 }, - { PCIE_VENDOR_ID_WCH, PCIE_DEVICE_ID_WCH_CH384_4S, PCI_ANY_ID, PCI_ANY_ID, 0, 0, pbn_wch384_4 }, diff --git a/drivers/tty/serial/atmel_serial.c b/drivers/tty/serial/atmel_serial.c index 846552bff67d..4e959c43f680 100644 --- a/drivers/tty/serial/atmel_serial.c +++ b/drivers/tty/serial/atmel_serial.c @@ -47,6 +47,7 @@ #include <linux/gpio/consumer.h> #include <linux/err.h> #include <linux/irq.h> +#include <linux/suspend.h> #include <asm/io.h> #include <asm/ioctls.h> @@ -173,6 +174,12 @@ struct atmel_uart_port { bool ms_irq_enabled; bool is_usart; /* usart or uart */ struct timer_list uart_timer; /* uart timer */ + + bool suspended; + unsigned int pending; + unsigned int pending_status; + spinlock_t lock_suspended; + int (*prepare_rx)(struct uart_port *port); int (*prepare_tx)(struct uart_port *port); void (*schedule_rx)(struct uart_port *port); @@ -1179,12 +1186,15 @@ static irqreturn_t atmel_interrupt(int irq, void *dev_id) { struct uart_port *port = dev_id; struct atmel_uart_port *atmel_port = to_atmel_uart_port(port); - unsigned int status, pending, pass_counter = 0; + unsigned int status, pending, mask, pass_counter = 0; bool gpio_handled = false; + spin_lock(&atmel_port->lock_suspended); + do { status = atmel_get_lines_status(port); - pending = status & UART_GET_IMR(port); + mask = UART_GET_IMR(port); + pending = status & mask; if (!gpio_handled) { /* * Dealing with GPIO interrupt @@ -1206,11 +1216,21 @@ static irqreturn_t atmel_interrupt(int irq, void *dev_id) if (!pending) break; + if (atmel_port->suspended) { + atmel_port->pending |= pending; + atmel_port->pending_status = status; + UART_PUT_IDR(port, mask); + pm_system_wakeup(); + break; + } + atmel_handle_receive(port, pending); atmel_handle_status(port, pending, status); atmel_handle_transmit(port, pending); } while (pass_counter++ < ATMEL_ISR_PASS_LIMIT); + spin_unlock(&atmel_port->lock_suspended); + return pass_counter ? IRQ_HANDLED : IRQ_NONE; } @@ -1742,7 +1762,8 @@ static int atmel_startup(struct uart_port *port) /* * Allocate the IRQ */ - retval = request_irq(port->irq, atmel_interrupt, IRQF_SHARED, + retval = request_irq(port->irq, atmel_interrupt, + IRQF_SHARED | IRQF_COND_SUSPEND, tty ? tty->name : "atmel_serial", port); if (retval) { dev_err(port->dev, "atmel_startup - Can't get irq\n"); @@ -2513,8 +2534,14 @@ static int atmel_serial_suspend(struct platform_device *pdev, /* we can not wake up if we're running on slow clock */ atmel_port->may_wakeup = device_may_wakeup(&pdev->dev); - if (atmel_serial_clk_will_stop()) + if (atmel_serial_clk_will_stop()) { + unsigned long flags; + + spin_lock_irqsave(&atmel_port->lock_suspended, flags); + atmel_port->suspended = true; + spin_unlock_irqrestore(&atmel_port->lock_suspended, flags); device_set_wakeup_enable(&pdev->dev, 0); + } uart_suspend_port(&atmel_uart, port); @@ -2525,6 +2552,18 @@ static int atmel_serial_resume(struct platform_device *pdev) { struct uart_port *port = platform_get_drvdata(pdev); struct atmel_uart_port *atmel_port = to_atmel_uart_port(port); + unsigned long flags; + + spin_lock_irqsave(&atmel_port->lock_suspended, flags); + if (atmel_port->pending) { + atmel_handle_receive(port, atmel_port->pending); + atmel_handle_status(port, atmel_port->pending, + atmel_port->pending_status); + atmel_handle_transmit(port, atmel_port->pending); + atmel_port->pending = 0; + } + atmel_port->suspended = false; + spin_unlock_irqrestore(&atmel_port->lock_suspended, flags); uart_resume_port(&atmel_uart, port); device_set_wakeup_enable(&pdev->dev, atmel_port->may_wakeup); @@ -2593,6 +2632,8 @@ static int atmel_serial_probe(struct platform_device *pdev) port->backup_imr = 0; port->uart.line = ret; + spin_lock_init(&port->lock_suspended); + ret = atmel_init_gpios(port, &pdev->dev); if (ret < 0) dev_err(&pdev->dev, "%s", diff --git a/drivers/tty/serial/fsl_lpuart.c b/drivers/tty/serial/fsl_lpuart.c index b1893f3f88f1..3ad1458bfeb0 100644 --- a/drivers/tty/serial/fsl_lpuart.c +++ b/drivers/tty/serial/fsl_lpuart.c @@ -921,6 +921,9 @@ static void lpuart_setup_watermark(struct lpuart_port *sport) writeb(val | UARTPFIFO_TXFE | UARTPFIFO_RXFE, sport->port.membase + UARTPFIFO); + /* explicitly clear RDRF */ + readb(sport->port.membase + UARTSR1); + /* flush Tx and Rx FIFO */ writeb(UARTCFIFO_TXFLUSH | UARTCFIFO_RXFLUSH, sport->port.membase + UARTCFIFO); @@ -1076,6 +1079,8 @@ static int lpuart_startup(struct uart_port *port) sport->txfifo_size = 0x1 << (((temp >> UARTPFIFO_TXSIZE_OFF) & UARTPFIFO_FIFOSIZE_MASK) + 1); + sport->port.fifosize = sport->txfifo_size; + sport->rxfifo_size = 0x1 << (((temp >> UARTPFIFO_RXSIZE_OFF) & UARTPFIFO_FIFOSIZE_MASK) + 1); diff --git a/drivers/tty/serial/of_serial.c b/drivers/tty/serial/of_serial.c index 7ff61e24a195..33fb94f78967 100644 --- a/drivers/tty/serial/of_serial.c +++ b/drivers/tty/serial/of_serial.c @@ -133,10 +133,6 @@ static int of_platform_serial_setup(struct platform_device *ofdev, if (of_find_property(np, "no-loopback-test", NULL)) port->flags |= UPF_SKIP_TEST; - ret = of_alias_get_id(np, "serial"); - if (ret >= 0) - port->line = ret; - port->dev = &ofdev->dev; switch (type) { diff --git a/drivers/tty/serial/samsung.c b/drivers/tty/serial/samsung.c index af821a908720..cf08876922f1 100644 --- a/drivers/tty/serial/samsung.c +++ b/drivers/tty/serial/samsung.c @@ -963,6 +963,7 @@ static void s3c24xx_serial_shutdown(struct uart_port *port) free_irq(ourport->tx_irq, ourport); tx_enabled(port) = 0; ourport->tx_claimed = 0; + ourport->tx_mode = 0; } if (ourport->rx_claimed) { diff --git a/drivers/tty/serial/sprd_serial.c b/drivers/tty/serial/sprd_serial.c index 594b63331ef4..bca975f5093b 100644 --- a/drivers/tty/serial/sprd_serial.c +++ b/drivers/tty/serial/sprd_serial.c @@ -293,8 +293,10 @@ static irqreturn_t sprd_handle_irq(int irq, void *dev_id) ims = serial_in(port, SPRD_IMSR); - if (!ims) + if (!ims) { + spin_unlock(&port->lock); return IRQ_NONE; + } serial_out(port, SPRD_ICLR, ~0); diff --git a/drivers/tty/sysrq.c b/drivers/tty/sysrq.c index 259a4d5a4e8f..843f2cdc280b 100644 --- a/drivers/tty/sysrq.c +++ b/drivers/tty/sysrq.c @@ -275,6 +275,7 @@ static struct sysrq_key_op sysrq_showregs_op = { static void sysrq_handle_showstate(int key) { show_state(); + show_workqueue_state(); } static struct sysrq_key_op sysrq_showstate_op = { .handler = sysrq_handle_showstate, diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c index 51f066aa375e..2bb4dfc02873 100644 --- a/drivers/tty/tty_io.c +++ b/drivers/tty/tty_io.c @@ -1028,8 +1028,8 @@ EXPORT_SYMBOL(start_tty); /* We limit tty time update visibility to every 8 seconds or so. */ static void tty_update_time(struct timespec *time) { - unsigned long sec = get_seconds() & ~7; - if ((long)(sec - time->tv_sec) > 0) + unsigned long sec = get_seconds(); + if (abs(sec - time->tv_sec) & ~7) time->tv_sec = sec; } diff --git a/drivers/tty/tty_ioctl.c b/drivers/tty/tty_ioctl.c index a5cf253b2544..632fc8152061 100644 --- a/drivers/tty/tty_ioctl.c +++ b/drivers/tty/tty_ioctl.c @@ -217,11 +217,17 @@ void tty_wait_until_sent(struct tty_struct *tty, long timeout) #endif if (!timeout) timeout = MAX_SCHEDULE_TIMEOUT; - if (wait_event_interruptible_timeout(tty->write_wait, - !tty_chars_in_buffer(tty), timeout) >= 0) { - if (tty->ops->wait_until_sent) - tty->ops->wait_until_sent(tty, timeout); - } + + timeout = wait_event_interruptible_timeout(tty->write_wait, + !tty_chars_in_buffer(tty), timeout); + if (timeout <= 0) + return; + + if (timeout == MAX_SCHEDULE_TIMEOUT) + timeout = 0; + + if (tty->ops->wait_until_sent) + tty->ops->wait_until_sent(tty, timeout); } EXPORT_SYMBOL(tty_wait_until_sent); |
