dm: video: Add a uclass for the text console

The existing LCD/video interface suffers from conflating the bitmap display
with text output on that display. As a result the implementation is more
complex than it needs to me.

We can support multiple text console drivers. Create a separate uclass to
support this, with its own API.

Signed-off-by: Simon Glass <sjg@chromium.org>
Acked-by: Anatolij Gustschin <agust@denx.de>
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index 7192013..e82f1ae 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -7,7 +7,7 @@
 
 ifdef CONFIG_DM
 obj-$(CONFIG_DISPLAY_PORT) += dp-uclass.o
-obj-$(CONFIG_DM_VIDEO) += video-uclass.o
+obj-$(CONFIG_DM_VIDEO) += video-uclass.o vidconsole-uclass.o
 endif
 
 obj-$(CONFIG_ATI_RADEON_FB) += ati_radeon_fb.o videomodes.o
diff --git a/drivers/video/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c
new file mode 100644
index 0000000..ea10189
--- /dev/null
+++ b/drivers/video/vidconsole-uclass.c
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2015 Google, Inc
+ * (C) Copyright 2001-2015
+ * DENX Software Engineering -- wd@denx.de
+ * Compulab Ltd - http://compulab.co.il/
+ * Bernecker & Rainer Industrieelektronik GmbH - http://www.br-automation.com
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <video.h>
+#include <video_console.h>
+#include <video_font.h>		/* Get font data, width and height */
+
+/* By default we scroll by a single line */
+#ifndef CONFIG_CONSOLE_SCROLL_LINES
+#define CONFIG_CONSOLE_SCROLL_LINES 1
+#endif
+
+int vidconsole_putc_xy(struct udevice *dev, uint x, uint y, char ch)
+{
+	struct vidconsole_ops *ops = vidconsole_get_ops(dev);
+
+	if (!ops->putc_xy)
+		return -ENOSYS;
+	return ops->putc_xy(dev, x, y, ch);
+}
+
+int vidconsole_move_rows(struct udevice *dev, uint rowdst, uint rowsrc,
+			 uint count)
+{
+	struct vidconsole_ops *ops = vidconsole_get_ops(dev);
+
+	if (!ops->move_rows)
+		return -ENOSYS;
+	return ops->move_rows(dev, rowdst, rowsrc, count);
+}
+
+int vidconsole_set_row(struct udevice *dev, uint row, int clr)
+{
+	struct vidconsole_ops *ops = vidconsole_get_ops(dev);
+
+	if (!ops->set_row)
+		return -ENOSYS;
+	return ops->set_row(dev, row, clr);
+}
+
+/* Move backwards one space */
+static void vidconsole_back(struct udevice *dev)
+{
+	struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
+
+	if (--priv->curr_col < 0) {
+		priv->curr_col = priv->cols - 1;
+		if (--priv->curr_row < 0)
+			priv->curr_row = 0;
+	}
+
+	vidconsole_putc_xy(dev, priv->curr_col * VIDEO_FONT_WIDTH,
+			   priv->curr_row * VIDEO_FONT_HEIGHT, ' ');
+}
+
+/* Move to a newline, scrolling the display if necessary */
+static void vidconsole_newline(struct udevice *dev)
+{
+	struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
+	struct udevice *vid_dev = dev->parent;
+	struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev);
+	const int rows = CONFIG_CONSOLE_SCROLL_LINES;
+	int i;
+
+	priv->curr_col = 0;
+
+	/* Check if we need to scroll the terminal */
+	if (++priv->curr_row >= priv->rows) {
+		vidconsole_move_rows(dev, 0, rows, priv->rows - rows);
+		for (i = 0; i < rows; i++)
+			vidconsole_set_row(dev, priv->rows - i - 1,
+					   vid_priv->colour_bg);
+		priv->curr_row -= rows;
+	}
+	video_sync(dev->parent);
+}
+
+int vidconsole_put_char(struct udevice *dev, char ch)
+{
+	struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
+	int ret;
+
+	switch (ch) {
+	case '\r':
+		priv->curr_col = 0;
+		break;
+	case '\n':
+		vidconsole_newline(dev);
+		break;
+	case '\t':	/* Tab (8 chars alignment) */
+		priv->curr_col +=  8;
+		priv->curr_col &= ~7;
+
+		if (priv->curr_col >= priv->cols)
+			vidconsole_newline(dev);
+		break;
+	case '\b':
+		vidconsole_back(dev);
+		break;
+	default:
+		/*
+		 * Failure of this function normally indicates an unsupported
+		 * colour depth. Check this and return an error to help with
+		 * diagnosis.
+		 */
+		ret = vidconsole_putc_xy(dev,
+					 priv->curr_col * VIDEO_FONT_WIDTH,
+					 priv->curr_row * VIDEO_FONT_HEIGHT,
+					 ch);
+		if (ret)
+			return ret;
+		if (++priv->curr_col >= priv->cols)
+			vidconsole_newline(dev);
+		break;
+	}
+
+	return 0;
+}
+
+static void vidconsole_putc(struct stdio_dev *sdev, const char ch)
+{
+	struct udevice *dev = sdev->priv;
+
+	vidconsole_put_char(dev, ch);
+}
+
+static void vidconsole_puts(struct stdio_dev *sdev, const char *s)
+{
+	struct udevice *dev = sdev->priv;
+
+	while (*s)
+		vidconsole_put_char(dev, *s++);
+}
+
+/* Set up the number of rows and colours (rotated drivers override this) */
+static int vidconsole_pre_probe(struct udevice *dev)
+{
+	struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
+	struct udevice *vid = dev->parent;
+	struct video_priv *vid_priv = dev_get_uclass_priv(vid);
+
+	priv->rows = vid_priv->ysize / VIDEO_FONT_HEIGHT;
+	priv->cols = vid_priv->xsize / VIDEO_FONT_WIDTH;
+
+	return 0;
+}
+
+/* Register the device with stdio */
+static int vidconsole_post_probe(struct udevice *dev)
+{
+	struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
+	struct stdio_dev *sdev = &priv->sdev;
+	int ret;
+
+	strlcpy(sdev->name, dev->name, sizeof(sdev->name));
+	sdev->flags = DEV_FLAGS_OUTPUT;
+	sdev->putc = vidconsole_putc;
+	sdev->puts = vidconsole_puts;
+	sdev->priv = dev;
+	ret = stdio_register(sdev);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+UCLASS_DRIVER(vidconsole) = {
+	.id		= UCLASS_VIDEO_CONSOLE,
+	.name		= "vidconsole0",
+	.pre_probe	= vidconsole_pre_probe,
+	.post_probe	= vidconsole_post_probe,
+	.per_device_auto_alloc_size	= sizeof(struct vidconsole_priv),
+};
+
+void vidconsole_position_cursor(struct udevice *dev, unsigned col, unsigned row)
+{
+	struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
+
+	priv->curr_col = min_t(short, col, priv->cols - 1);
+	priv->curr_row = min_t(short, row, priv->rows - 1);
+}
+
+static int do_video_setcursor(cmd_tbl_t *cmdtp, int flag, int argc,
+			      char *const argv[])
+{
+	unsigned int col, row;
+	struct udevice *dev;
+
+	if (argc != 3)
+		return CMD_RET_USAGE;
+
+	uclass_first_device(UCLASS_VIDEO_CONSOLE, &dev);
+	if (!dev)
+		return CMD_RET_FAILURE;
+	col = simple_strtoul(argv[1], NULL, 10);
+	row = simple_strtoul(argv[2], NULL, 10);
+	vidconsole_position_cursor(dev, col, row);
+
+	return 0;
+}
+
+static int do_video_puts(cmd_tbl_t *cmdtp, int flag, int argc,
+			 char *const argv[])
+{
+	struct udevice *dev;
+	const char *s;
+
+	if (argc != 2)
+		return CMD_RET_USAGE;
+
+	uclass_first_device(UCLASS_VIDEO_CONSOLE, &dev);
+	if (!dev)
+		return CMD_RET_FAILURE;
+	for (s = argv[1]; *s; s++)
+		vidconsole_put_char(dev, *s);
+
+	return 0;
+}
+
+U_BOOT_CMD(
+	setcurs, 3,	1,	do_video_setcursor,
+	"set cursor position within screen",
+	"    <col> <row> in character"
+);
+
+U_BOOT_CMD(
+	lcdputs, 2,	1,	do_video_puts,
+	"print string on video framebuffer",
+	"    <string>"
+);
diff --git a/drivers/video/video-uclass.c b/drivers/video/video-uclass.c
index 1615889..63d0d9d 100644
--- a/drivers/video/video-uclass.c
+++ b/drivers/video/video-uclass.c
@@ -189,6 +189,27 @@
 #endif
 	video_clear(dev);
 
+	/*
+	 * Create a text console devices. For now we always do this, although
+	 * it might be useful to support only bitmap drawing on the device
+	 * for boards that don't need to display text.
+	 */
+	snprintf(name, sizeof(name), "%s.vidconsole", dev->name);
+	str = strdup(name);
+	if (!str)
+		return -ENOMEM;
+	snprintf(drv, sizeof(drv), "vidconsole%d", priv->rot);
+	ret = device_bind_driver(dev, drv, str, &cons);
+	if (ret) {
+		debug("%s: Cannot bind console driver\n", __func__);
+		return ret;
+	}
+	ret = device_probe(cons);
+	if (ret) {
+		debug("%s: Cannot probe console driver\n", __func__);
+		return ret;
+	}
+
 	return 0;
 };