Skip to content
hvsi.c 30.6 KiB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
}

/*
 * I have never seen throttle or unthrottle called, so this little throttle
 * buffering scheme may or may not work.
 */
static void hvsi_throttle(struct tty_struct *tty)
{
	struct hvsi_struct *hp = tty->driver_data;
Linus Torvalds's avatar
Linus Torvalds committed

	pr_debug("%s\n", __func__);
Linus Torvalds's avatar
Linus Torvalds committed

	h_vio_signal(hp->vtermno, VIO_IRQ_DISABLE);
}

static void hvsi_unthrottle(struct tty_struct *tty)
{
	struct hvsi_struct *hp = tty->driver_data;
Linus Torvalds's avatar
Linus Torvalds committed
	unsigned long flags;
	int shouldflip = 0;

	pr_debug("%s\n", __func__);
Linus Torvalds's avatar
Linus Torvalds committed

	spin_lock_irqsave(&hp->lock, flags);
	if (hp->n_throttle) {
		hvsi_send_overflow(hp);
		shouldflip = 1;
	}
	spin_unlock_irqrestore(&hp->lock, flags);

	if (shouldflip)
		tty_flip_buffer_push(hp->tty);

	h_vio_signal(hp->vtermno, VIO_IRQ_ENABLE);
}

static int hvsi_tiocmget(struct tty_struct *tty)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct hvsi_struct *hp = tty->driver_data;
Linus Torvalds's avatar
Linus Torvalds committed

	hvsi_get_mctrl(hp);
	return hp->mctrl;
}

static int hvsi_tiocmset(struct tty_struct *tty,
				unsigned int set, unsigned int clear)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct hvsi_struct *hp = tty->driver_data;
Linus Torvalds's avatar
Linus Torvalds committed
	unsigned long flags;
	uint16_t new_mctrl;

	/* we can only alter DTR */
	clear &= TIOCM_DTR;
	set &= TIOCM_DTR;

	spin_lock_irqsave(&hp->lock, flags);

	new_mctrl = (hp->mctrl & ~clear) | set;

	if (hp->mctrl != new_mctrl) {
		hvsi_set_mctrl(hp, new_mctrl);
		hp->mctrl = new_mctrl;
	}
	spin_unlock_irqrestore(&hp->lock, flags);

	return 0;
}


static const struct tty_operations hvsi_ops = {
Linus Torvalds's avatar
Linus Torvalds committed
	.open = hvsi_open,
	.close = hvsi_close,
	.write = hvsi_write,
	.hangup = hvsi_hangup,
	.write_room = hvsi_write_room,
	.chars_in_buffer = hvsi_chars_in_buffer,
	.throttle = hvsi_throttle,
	.unthrottle = hvsi_unthrottle,
	.tiocmget = hvsi_tiocmget,
	.tiocmset = hvsi_tiocmset,
};

static int __init hvsi_init(void)
{
	int i;

	hvsi_driver = alloc_tty_driver(hvsi_count);
	if (!hvsi_driver)
		return -ENOMEM;

	hvsi_driver->owner = THIS_MODULE;
	hvsi_driver->driver_name = "hvsi";
	hvsi_driver->name = "hvsi";
	hvsi_driver->major = HVSI_MAJOR;
	hvsi_driver->minor_start = HVSI_MINOR;
	hvsi_driver->type = TTY_DRIVER_TYPE_SYSTEM;
	hvsi_driver->init_termios = tty_std_termios;
	hvsi_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL;
	hvsi_driver->init_termios.c_ispeed = 9600;
	hvsi_driver->init_termios.c_ospeed = 9600;
Linus Torvalds's avatar
Linus Torvalds committed
	hvsi_driver->flags = TTY_DRIVER_REAL_RAW;
	tty_set_operations(hvsi_driver, &hvsi_ops);

	for (i=0; i < hvsi_count; i++) {
		struct hvsi_struct *hp = &hvsi_ports[i];
		int ret = 1;

		ret = request_irq(hp->virq, hvsi_interrupt, IRQF_DISABLED, "hvsi", hp);
Linus Torvalds's avatar
Linus Torvalds committed
		if (ret)
			printk(KERN_ERR "HVSI: couldn't reserve irq 0x%x (error %i)\n",
				hp->virq, ret);
	}
	hvsi_wait = wait_for_state; /* irqs active now */

	if (tty_register_driver(hvsi_driver))
		panic("Couldn't register hvsi console driver\n");

	printk(KERN_DEBUG "HVSI: registered %i devices\n", hvsi_count);
Linus Torvalds's avatar
Linus Torvalds committed

	return 0;
}
device_initcall(hvsi_init);

/***** console (not tty) code: *****/

static void hvsi_console_print(struct console *console, const char *buf,
		unsigned int count)
{
	struct hvsi_struct *hp = &hvsi_ports[console->index];
	char c[HVSI_MAX_OUTGOING_DATA] __ALIGNED__;
	unsigned int i = 0, n = 0;
	int ret, donecr = 0;

	mb();
	if (!is_open(hp))
		return;

	/*
	 * ugh, we have to translate LF -> CRLF ourselves, in place.
	 * copied from hvc_console.c:
	 */
	while (count > 0 || i > 0) {
		if (count > 0 && i < sizeof(c)) {
			if (buf[n] == '\n' && !donecr) {
				c[i++] = '\r';
				donecr = 1;
			} else {
				c[i++] = buf[n++];
				donecr = 0;
				--count;
			}
		} else {
			ret = hvsi_put_chars(hp, c, i);
			if (ret < 0)
				i = 0;
			i -= ret;
		}
	}
}

static struct tty_driver *hvsi_console_device(struct console *console,
	int *index)
{
	*index = console->index;
	return hvsi_driver;
}

static int __init hvsi_console_setup(struct console *console, char *options)
{
	struct hvsi_struct *hp;
Linus Torvalds's avatar
Linus Torvalds committed
	int ret;

	if (console->index < 0 || console->index >= hvsi_count)
		return -1;
	hp = &hvsi_ports[console->index];
Linus Torvalds's avatar
Linus Torvalds committed

	/* give the FSP a chance to change the baud rate when we re-open */
	hvsi_close_protocol(hp);

	ret = hvsi_handshake(hp);
	if (ret < 0)
		return ret;

	ret = hvsi_get_mctrl(hp);
	if (ret < 0)
		return ret;

	ret = hvsi_set_mctrl(hp, hp->mctrl | TIOCM_DTR);
	if (ret < 0)
		return ret;

	hp->flags |= HVSI_CONSOLE;

	return 0;
}

static struct console hvsi_console = {
Linus Torvalds's avatar
Linus Torvalds committed
	.name		= "hvsi",
	.write		= hvsi_console_print,
	.device		= hvsi_console_device,
	.setup		= hvsi_console_setup,
	.flags		= CON_PRINTBUFFER,
	.index		= -1,
};

static int __init hvsi_console_init(void)
{
	struct device_node *vty;

	hvsi_wait = poll_for_state; /* no irqs yet; must poll */

	/* search device tree for vty nodes */
	for (vty = of_find_compatible_node(NULL, "serial", "hvterm-protocol");
			vty != NULL;
			vty = of_find_compatible_node(vty, "serial", "hvterm-protocol")) {
		struct hvsi_struct *hp;
		const uint32_t *vtermno, *irq;
Linus Torvalds's avatar
Linus Torvalds committed

		vtermno = of_get_property(vty, "reg", NULL);
		irq = of_get_property(vty, "interrupts", NULL);
Linus Torvalds's avatar
Linus Torvalds committed
		if (!vtermno || !irq)
			continue;

		if (hvsi_count >= MAX_NR_HVSI_CONSOLES) {
			of_node_put(vty);
			break;
		}

		hp = &hvsi_ports[hvsi_count];
		INIT_DELAYED_WORK(&hp->writer, hvsi_write_worker);
		INIT_WORK(&hp->handshaker, hvsi_handshaker);
Linus Torvalds's avatar
Linus Torvalds committed
		init_waitqueue_head(&hp->emptyq);
		init_waitqueue_head(&hp->stateq);
		spin_lock_init(&hp->lock);
		hp->index = hvsi_count;
		hp->inbuf_end = hp->inbuf;
		hp->state = HVSI_CLOSED;
		hp->vtermno = *vtermno;
		hp->virq = irq_create_mapping(NULL, irq[0]);
Linus Torvalds's avatar
Linus Torvalds committed
		if (hp->virq == NO_IRQ) {
			printk(KERN_ERR "%s: couldn't create irq mapping for 0x%x\n",
Linus Torvalds's avatar
Linus Torvalds committed
			continue;
Linus Torvalds's avatar
Linus Torvalds committed

		hvsi_count++;
	}

	if (hvsi_count)
		register_console(&hvsi_console);
Linus Torvalds's avatar
Linus Torvalds committed
	return 0;
}
console_initcall(hvsi_console_init);