diff options
| -rw-r--r-- | drivers/char/Kconfig | 4 | ||||
| -rw-r--r-- | drivers/char/Makefile | 1 | ||||
| -rw-r--r-- | drivers/char/dcc_tty.c | 326 | 
3 files changed, 331 insertions, 0 deletions
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index ddbed25c7af..6fcb9b01e57 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -597,6 +597,10 @@ config DEVPORT  	depends on ISA || PCI  	default y +config DCC_TTY +	tristate "DCC tty driver" +	depends on ARM +  source "drivers/s390/char/Kconfig"  config MSM_SMD_PKT diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 7ff1d0d208a..e0047ed1e74 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -56,6 +56,7 @@ obj-$(CONFIG_PCMCIA)		+= pcmcia/  obj-$(CONFIG_HANGCHECK_TIMER)	+= hangcheck-timer.o  obj-$(CONFIG_TCG_TPM)		+= tpm/ +obj-$(CONFIG_DCC_TTY)		+= dcc_tty.o  obj-$(CONFIG_PS3_FLASH)		+= ps3flash.o  obj-$(CONFIG_JS_RTC)		+= js-rtc.o diff --git a/drivers/char/dcc_tty.c b/drivers/char/dcc_tty.c new file mode 100644 index 00000000000..a787accdcb1 --- /dev/null +++ b/drivers/char/dcc_tty.c @@ -0,0 +1,326 @@ +/* drivers/char/dcc_tty.c + * + * Copyright (C) 2007 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/console.h> +#include <linux/hrtimer.h> +#include <linux/tty.h> +#include <linux/tty_driver.h> +#include <linux/tty_flip.h> + +MODULE_DESCRIPTION("DCC TTY Driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0"); + +static spinlock_t g_dcc_tty_lock = SPIN_LOCK_UNLOCKED; +static struct hrtimer g_dcc_timer; +static char g_dcc_buffer[16]; +static int g_dcc_buffer_head; +static int g_dcc_buffer_count; +static unsigned g_dcc_write_delay_usecs = 1; +static struct tty_driver *g_dcc_tty_driver; +static struct tty_struct *g_dcc_tty; +static int g_dcc_tty_open_count; + +static void dcc_poll_locked(void) +{ +	char ch; +	int rch; +	int written; + +	while (g_dcc_buffer_count) { +		ch = g_dcc_buffer[g_dcc_buffer_head]; +		asm( +			"mrc 14, 0, r15, c0, c1, 0\n" +			"mcrcc 14, 0, %1, c0, c5, 0\n" +			"movcc %0, #1\n" +			"movcs %0, #0\n" +			: "=r" (written) +			: "r" (ch) +		); +		if (written) { +			if (ch == '\n') +				g_dcc_buffer[g_dcc_buffer_head] = '\r'; +			else { +				g_dcc_buffer_head = (g_dcc_buffer_head + 1) % ARRAY_SIZE(g_dcc_buffer); +				g_dcc_buffer_count--; +				if (g_dcc_tty) +					tty_wakeup(g_dcc_tty); +			} +			g_dcc_write_delay_usecs = 1; +		} else { +			if (g_dcc_write_delay_usecs > 0x100) +				break; +			g_dcc_write_delay_usecs <<= 1; +			udelay(g_dcc_write_delay_usecs); +		} +	} + +	if (g_dcc_tty && !test_bit(TTY_THROTTLED, &g_dcc_tty->flags)) { +		asm( +			"mrc 14, 0, %0, c0, c1, 0\n" +			"tst %0, #(1 << 30)\n" +			"moveq %0, #-1\n" +			"mrcne 14, 0, %0, c0, c5, 0\n" +			: "=r" (rch) +		); +		if (rch >= 0) { +			ch = rch; +			tty_insert_flip_string(g_dcc_tty, &ch, 1); +			tty_flip_buffer_push(g_dcc_tty); +		} +	} + + +	if (g_dcc_buffer_count) +		hrtimer_start(&g_dcc_timer, ktime_set(0, g_dcc_write_delay_usecs * NSEC_PER_USEC), HRTIMER_MODE_REL); +	else +		hrtimer_start(&g_dcc_timer, ktime_set(0, 20 * NSEC_PER_MSEC), HRTIMER_MODE_REL); +} + +static int dcc_tty_open(struct tty_struct * tty, struct file * filp) +{ +	int ret; +	unsigned long irq_flags; + +	spin_lock_irqsave(&g_dcc_tty_lock, irq_flags); +	if (g_dcc_tty == NULL || g_dcc_tty == tty) { +		g_dcc_tty = tty; +		g_dcc_tty_open_count++; +		ret = 0; +	} else +		ret = -EBUSY; +	spin_unlock_irqrestore(&g_dcc_tty_lock, irq_flags); + +	printk("dcc_tty_open, tty %p, f_flags %x, returned %d\n", tty, filp->f_flags, ret); + +	return ret; +} + +static void dcc_tty_close(struct tty_struct * tty, struct file * filp) +{ +	printk("dcc_tty_close, tty %p, f_flags %x\n", tty, filp->f_flags); +	if (g_dcc_tty == tty) { +		if (--g_dcc_tty_open_count == 0) +			g_dcc_tty = NULL; +	} +} + +static int dcc_write(const unsigned char *buf_start, int count) +{ +	const unsigned char *buf = buf_start; +	unsigned long irq_flags; +	int copy_len; +	int space_left; +	int tail; + +	if (count < 1) +		return 0; + +	spin_lock_irqsave(&g_dcc_tty_lock, irq_flags); +	do { +		tail = (g_dcc_buffer_head + g_dcc_buffer_count) % ARRAY_SIZE(g_dcc_buffer); +		copy_len = ARRAY_SIZE(g_dcc_buffer) - tail; +		space_left = ARRAY_SIZE(g_dcc_buffer) - g_dcc_buffer_count; +		if (copy_len > space_left) +			copy_len = space_left; +		if (copy_len > count) +			copy_len = count; +		memcpy(&g_dcc_buffer[tail], buf, copy_len); +		g_dcc_buffer_count += copy_len; +		buf += copy_len; +		count -= copy_len; +		if (copy_len < count && copy_len < space_left) { +			space_left -= copy_len; +			copy_len = count; +			if (copy_len > space_left) { +				copy_len = space_left; +			} +			memcpy(g_dcc_buffer, buf, copy_len); +			buf += copy_len; +			count -= copy_len; +			g_dcc_buffer_count += copy_len; +		} +		dcc_poll_locked(); +		space_left = ARRAY_SIZE(g_dcc_buffer) - g_dcc_buffer_count; +	} while(count && space_left); +	spin_unlock_irqrestore(&g_dcc_tty_lock, irq_flags); +	return buf - buf_start; +} + +static int dcc_tty_write(struct tty_struct * tty, const unsigned char *buf, int count) +{ +	int ret; +	/* printk("dcc_tty_write %p, %d\n", buf, count); */ +	ret = dcc_write(buf, count); +	if (ret != count) +		printk("dcc_tty_write %p, %d, returned %d\n", buf, count, ret); +	return ret; +} + +static int dcc_tty_write_room(struct tty_struct *tty) +{ +	int space_left; +	unsigned long irq_flags; + +	spin_lock_irqsave(&g_dcc_tty_lock, irq_flags); +	space_left = ARRAY_SIZE(g_dcc_buffer) - g_dcc_buffer_count; +	spin_unlock_irqrestore(&g_dcc_tty_lock, irq_flags); +	return space_left; +} + +static int dcc_tty_chars_in_buffer(struct tty_struct *tty) +{ +	int ret; +	asm( +		"mrc 14, 0, %0, c0, c1, 0\n" +		"mov %0, %0, LSR #30\n" +		"and %0, %0, #1\n" +		: "=r" (ret) +	); +	return ret; +} + +static void dcc_tty_unthrottle(struct tty_struct * tty) +{ +	unsigned long irq_flags; + +	spin_lock_irqsave(&g_dcc_tty_lock, irq_flags); +	dcc_poll_locked(); +	spin_unlock_irqrestore(&g_dcc_tty_lock, irq_flags); +} + +static enum hrtimer_restart dcc_tty_timer_func(struct hrtimer *timer) +{ +	unsigned long irq_flags; + +	spin_lock_irqsave(&g_dcc_tty_lock, irq_flags); +	dcc_poll_locked(); +	spin_unlock_irqrestore(&g_dcc_tty_lock, irq_flags); +	return HRTIMER_NORESTART; +} + +void dcc_console_write(struct console *co, const char *b, unsigned count) +{ +#if 1 +	dcc_write(b, count); +#else +	/* blocking printk */ +	while (count > 0) { +		int written; +		written = dcc_write(b, count); +		if (written) { +			b += written; +			count -= written; +		} +	} +#endif +} + +static struct tty_driver *dcc_console_device(struct console *c, int *index) +{ +	*index = 0; +	return g_dcc_tty_driver; +} + +static int __init dcc_console_setup(struct console *co, char *options) +{ +	if (co->index != 0) +		return -ENODEV; +	return 0; +} + + +static struct console dcc_console = +{ +	.name		= "ttyDCC", +	.write		= dcc_console_write, +	.device		= dcc_console_device, +	.setup		= dcc_console_setup, +	.flags		= CON_PRINTBUFFER, +	.index		= -1, +}; + +static struct tty_operations dcc_tty_ops = { +	.open = dcc_tty_open, +	.close = dcc_tty_close, +	.write = dcc_tty_write, +	.write_room = dcc_tty_write_room, +	.chars_in_buffer = dcc_tty_chars_in_buffer, +	.unthrottle = dcc_tty_unthrottle, +}; + +static int __init dcc_tty_init(void) +{ +	int ret; + +	hrtimer_init(&g_dcc_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); +	g_dcc_timer.function = dcc_tty_timer_func; + +	g_dcc_tty_driver = alloc_tty_driver(1); +	if (!g_dcc_tty_driver) { +		printk(KERN_ERR "dcc_tty_probe: alloc_tty_driver failed\n"); +		ret = -ENOMEM; +		goto err_alloc_tty_driver_failed; +	} +	g_dcc_tty_driver->owner = THIS_MODULE; +	g_dcc_tty_driver->driver_name = "dcc"; +	g_dcc_tty_driver->name = "ttyDCC"; +	g_dcc_tty_driver->major = 0; // auto assign +	g_dcc_tty_driver->minor_start = 0; +	g_dcc_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; +	g_dcc_tty_driver->subtype = SERIAL_TYPE_NORMAL; +	g_dcc_tty_driver->init_termios = tty_std_termios; +	g_dcc_tty_driver->flags = TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; +	tty_set_operations(g_dcc_tty_driver, &dcc_tty_ops); +	ret = tty_register_driver(g_dcc_tty_driver); +	if (ret) { +		printk(KERN_ERR "dcc_tty_probe: tty_register_driver failed, %d\n", ret); +		goto err_tty_register_driver_failed; +	} +	tty_register_device(g_dcc_tty_driver, 0, NULL); + +	register_console(&dcc_console); +	hrtimer_start(&g_dcc_timer, ktime_set(0, 0), HRTIMER_MODE_REL); + +	return 0; + +err_tty_register_driver_failed: +	put_tty_driver(g_dcc_tty_driver); +	g_dcc_tty_driver = NULL; +err_alloc_tty_driver_failed: +	return ret; +} + +static void  __exit dcc_tty_exit(void) +{ +	int ret; + +	tty_unregister_device(g_dcc_tty_driver, 0); +	ret = tty_unregister_driver(g_dcc_tty_driver); +	if (ret < 0) { +		printk(KERN_ERR "dcc_tty_remove: tty_unregister_driver failed, %d\n", ret); +	} else { +		put_tty_driver(g_dcc_tty_driver); +	} +	g_dcc_tty_driver = NULL; +} + +module_init(dcc_tty_init); +module_exit(dcc_tty_exit); + +  |