video_osd: Add ihs_video_out driver
authorMario Six <mario.six@gdsys.cc>
Thu, 27 Sep 2018 07:19:30 +0000 (09:19 +0200)
committerAnatolij Gustschin <agust@denx.de>
Fri, 28 Sep 2018 16:26:44 +0000 (18:26 +0200)
Add a driver for IHS OSDs on IHS FPGAs.

Reviewed-by: Simon Glass <sjg@chromium.org>
Signed-off-by: Mario Six <mario.six@gdsys.cc>
doc/device-tree-bindings/video/osd/gdsys,ihs_video_out.txt [new file with mode: 0644]
drivers/video/Kconfig
drivers/video/Makefile
drivers/video/ihs_video_out.c [new file with mode: 0644]

diff --git a/doc/device-tree-bindings/video/osd/gdsys,ihs_video_out.txt b/doc/device-tree-bindings/video/osd/gdsys,ihs_video_out.txt
new file mode 100644 (file)
index 0000000..d7aacc8
--- /dev/null
@@ -0,0 +1,23 @@
+* Guntermann & Drunck Integrated Hardware Systems OSD
+
+Required properties:
+- compatible: "gdsys,ihs_video_out"
+- reg: A combination of three register spaces:
+       - Register base for the video registers
+       - Register base for the OSD registers
+       - Address of the OSD video memory
+- mode: The initial resolution and frequency: "1024_768_60", "720_400_70", or
+        "640_480_70"
+- clk_gen: phandle to the pixel clock generator
+- dp_tx: phandle to the display associated with the OSD
+
+Example:
+
+fpga0_video0 {
+       compatible = "gdsys,ihs_video_out";
+       reg = <0x100 0x40
+              0x180 0x20
+              0x1000 0x1000>;
+       dp_tx = <&fpga0_dp_video0>;
+       clk_gen = <&fpga0_video0_clkgen>;
+};
index 4e627a315b9ba6bb6003fb548e627669e9b6aea3..1e2cbe7afcfdda4f2435b3c775ee2e215bb5be0e 100644 (file)
@@ -691,4 +691,13 @@ config OSD
           This supports drivers that provide a OSD (on-screen display), which
           is a (usually text-oriented) graphics buffer to show information on
           a display.
+
+config IHS_VIDEO_OUT
+       bool "Enable IHS video out driver"
+       depends on OSD
+       help
+         Enable support for the gdsys Integrated Hardware Systems (IHS) video
+         out On-screen Display (OSD) used on gdsys FPGAs to control dynamic
+         textual overlays of the display outputs.
+
 endmenu
index 9cec564373ee45394f61b2e01ba5ab215569512e..290747ba94f810aa153311a9a9bf62396f174865 100644 (file)
@@ -28,6 +28,7 @@ obj-$(CONFIG_ATMEL_LCD) += atmel_lcdfb.o
 obj-$(CONFIG_CFB_CONSOLE) += cfb_console.o
 obj-$(CONFIG_FORMIKE) += formike.o
 obj-$(CONFIG_FSL_DIU_FB) += fsl_diu_fb.o videomodes.o
+obj-$(CONFIG_IHS_VIDEO_OUT) += ihs_video_out.o
 obj-$(CONFIG_LD9040) += ld9040.o
 obj-$(CONFIG_LG4573) += lg4573.o
 obj-$(CONFIG_LOGICORE_DP_TX) += logicore_dp_tx.o
diff --git a/drivers/video/ihs_video_out.c b/drivers/video/ihs_video_out.c
new file mode 100644 (file)
index 0000000..5cdf17a
--- /dev/null
@@ -0,0 +1,341 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2017
+ * Mario Six, Guntermann & Drunck GmbH, mario.six@gdsys.cc
+ *
+ * based on the gdsys osd driver, which is
+ *
+ * (C) Copyright 2010
+ * Dirk Eibach, Guntermann & Drunck GmbH, dirk.eibach@gdsys.de
+ */
+
+#include <common.h>
+#include <display.h>
+#include <dm.h>
+#include <regmap.h>
+#include <video_osd.h>
+#include <asm/gpio.h>
+
+static const uint MAX_X_CHARS = 53;
+static const uint MAX_Y_CHARS = 26;
+static const uint MAX_VIDEOMEM_WIDTH = 64;
+static const uint MAX_VIDEOMEM_HEIGHT = 32;
+static const uint CHAR_WIDTH = 12;
+static const uint CHAR_HEIGHT = 18;
+
+static const u16 BASE_WIDTH_MASK = 0x3f00;
+static const uint BASE_WIDTH_SHIFT = 8;
+static const u16 BASE_HEIGTH_MASK = 0x001f;
+static const uint BASE_HEIGTH_SHIFT;
+
+struct ihs_video_out_regs {
+       /* Device version register */
+       u16 versions;
+       /* Device feature register */
+       u16 features;
+       /* Device control register */
+       u16 control;
+       /* Register controlling screen size */
+       u16 xy_size;
+       /* Register controlling screen scaling */
+       u16 xy_scale;
+       /* Register controlling screen x position */
+       u16 x_pos;
+       /* Register controlling screen y position */
+       u16 y_pos;
+};
+
+#define ihs_video_out_set(map, member, val) \
+       regmap_range_set(map, 1, struct ihs_video_out_regs, member, val)
+
+#define ihs_video_out_get(map, member, valp) \
+       regmap_range_get(map, 1, struct ihs_video_out_regs, member, valp)
+
+enum {
+       CONTROL_FILTER_BLACK = (0 << 0),
+       CONTROL_FILTER_ORIGINAL = (1 << 0),
+       CONTROL_FILTER_DARKER = (2 << 0),
+       CONTROL_FILTER_GRAY = (3 << 0),
+
+       CONTROL_MODE_PASSTHROUGH = (0 << 3),
+       CONTROL_MODE_OSD = (1 << 3),
+       CONTROL_MODE_AUTO = (2 << 3),
+       CONTROL_MODE_OFF = (3 << 3),
+
+       CONTROL_ENABLE_OFF = (0 << 6),
+       CONTROL_ENABLE_ON = (1 << 6),
+};
+
+struct ihs_video_out_priv {
+       /* Register map for OSD device */
+       struct regmap *map;
+       /* Pointer to video memory */
+       u16 *vidmem;
+       /* Display width in text columns */
+       uint base_width;
+       /* Display height in text rows */
+       uint base_height;
+       /* x-resolution of the display in pixels */
+       uint res_x;
+       /* y-resolution of the display in pixels */
+       uint res_y;
+       /* OSD's sync mode (resolution + frequency) */
+       int sync_src;
+       /* The display port output for this OSD */
+       struct udevice *video_tx;
+       /* The pixel clock generator for the display */
+       struct udevice *clk_gen;
+};
+
+static const struct udevice_id ihs_video_out_ids[] = {
+       { .compatible = "gdsys,ihs_video_out" },
+       { }
+};
+
+/**
+ * set_control() - Set the control register to a given value
+ *
+ * The current value of sync_src is preserved by the function automatically.
+ *
+ * @dev: the OSD device whose control register to set
+ * @value: the 16-bit value to write to the control register
+ * Return: 0
+ */
+static int set_control(struct udevice *dev, u16 value)
+{
+       struct ihs_video_out_priv *priv = dev_get_priv(dev);
+
+       if (priv->sync_src)
+               value |= ((priv->sync_src & 0x7) << 8);
+
+       ihs_video_out_set(priv->map, control, value);
+
+       return 0;
+}
+
+int ihs_video_out_get_info(struct udevice *dev, struct video_osd_info *info)
+{
+       struct ihs_video_out_priv *priv = dev_get_priv(dev);
+       u16 versions;
+
+       ihs_video_out_get(priv->map, versions, &versions);
+
+       info->width = priv->base_width;
+       info->height = priv->base_height;
+       info->major_version = versions / 100;
+       info->minor_version = versions % 100;
+
+       return 0;
+}
+
+int ihs_video_out_set_mem(struct udevice *dev, uint col, uint row, u8 *buf,
+                         size_t buflen, uint count)
+{
+       struct ihs_video_out_priv *priv = dev_get_priv(dev);
+       int res;
+       uint offset;
+       uint k, rep;
+       u16 data;
+
+       /* Repetitions (controlled via count parmeter) */
+       for (rep = 0; rep < count; ++rep) {
+               offset = row * priv->base_width + col + rep * (buflen / 2);
+
+               /* Write a single buffer copy */
+               for (k = 0; k < buflen / 2; ++k) {
+                       uint max_size = priv->base_width * priv->base_height;
+
+                       if (offset + k >= max_size) {
+                               debug("%s: Write would be out of OSD bounds\n",
+                                     dev->name);
+                               return -E2BIG;
+                       }
+
+                       data = buf[2 * k + 1] + 256 * buf[2 * k];
+                       out_le16(priv->vidmem + offset + k, data);
+               }
+       }
+
+       res = set_control(dev, CONTROL_FILTER_ORIGINAL |
+                              CONTROL_MODE_OSD |
+                              CONTROL_ENABLE_ON);
+       if (res) {
+               debug("%s: Could not set control register\n", dev->name);
+               return res;
+       }
+
+       return 0;
+}
+
+/**
+ * div2_u16() - Approximately divide a 16-bit number by 2
+ *
+ * @val: The 16-bit value to divide by two
+ * Return: The approximate division of val by two
+ */
+static inline u16 div2_u16(u16 val)
+{
+       return (32767 * val) / 65535;
+}
+
+int ihs_video_out_set_size(struct udevice *dev, uint col, uint row)
+{
+       struct ihs_video_out_priv *priv = dev_get_priv(dev);
+
+       if (!col || col > MAX_VIDEOMEM_WIDTH || col > MAX_X_CHARS ||
+           !row || row > MAX_VIDEOMEM_HEIGHT || row > MAX_Y_CHARS) {
+               debug("%s: Desired OSD size invalid\n", dev->name);
+               return -EINVAL;
+       }
+
+       ihs_video_out_set(priv->map, xy_size, ((col - 1) << 8) | (row - 1));
+       /* Center OSD on screen */
+       ihs_video_out_set(priv->map, x_pos,
+                         div2_u16(priv->res_x - CHAR_WIDTH * col));
+       ihs_video_out_set(priv->map, y_pos,
+                         div2_u16(priv->res_y - CHAR_HEIGHT * row));
+
+       return 0;
+}
+
+int ihs_video_out_print(struct udevice *dev, uint col, uint row, ulong color,
+                       char *text)
+{
+       int res;
+       u8 buffer[2 * MAX_VIDEOMEM_WIDTH];
+       uint k;
+       uint charcount = strlen(text);
+       uint len = min(charcount, 2 * MAX_VIDEOMEM_WIDTH);
+
+       for (k = 0; k < len; ++k) {
+               buffer[2 * k] = text[k];
+               buffer[2 * k + 1] = color;
+       }
+
+       res = ihs_video_out_set_mem(dev, col, row, buffer, 2 * len, 1);
+       if (res < 0) {
+               debug("%s: Could not write to video memory\n", dev->name);
+               return res;
+       }
+
+       return 0;
+}
+
+static const struct video_osd_ops ihs_video_out_ops = {
+       .get_info = ihs_video_out_get_info,
+       .set_mem = ihs_video_out_set_mem,
+       .set_size = ihs_video_out_set_size,
+       .print = ihs_video_out_print,
+};
+
+int ihs_video_out_probe(struct udevice *dev)
+{
+       struct ihs_video_out_priv *priv = dev_get_priv(dev);
+       struct ofnode_phandle_args phandle_args;
+       const char *mode;
+       u16 features;
+       struct display_timing timing;
+       int res;
+
+       res = regmap_init_mem(dev_ofnode(dev), &priv->map);
+       if (!res) {
+               debug("%s: Could initialize regmap (err = %d)\n", dev->name,
+                     res);
+               return res;
+       }
+
+       /* Range with index 2 is video memory */
+       priv->vidmem = regmap_get_range(priv->map, 2);
+
+       mode = dev_read_string(dev, "mode");
+       if (!mode) {
+               debug("%s: Could not read mode property\n", dev->name);
+               return -EINVAL;
+       }
+
+       if (!strcmp(mode, "1024_768_60")) {
+               priv->sync_src = 2;
+               priv->res_x = 1024;
+               priv->res_y = 768;
+               timing.hactive.typ = 1024;
+               timing.vactive.typ = 768;
+       } else if (!strcmp(mode, "720_400_70")) {
+               priv->sync_src = 1;
+               priv->res_x = 720;
+               priv->res_y = 400;
+               timing.hactive.typ = 720;
+               timing.vactive.typ = 400;
+       } else {
+               priv->sync_src = 0;
+               priv->res_x = 640;
+               priv->res_y = 480;
+               timing.hactive.typ = 640;
+               timing.vactive.typ = 480;
+       }
+
+       ihs_video_out_get(priv->map, features, &features);
+
+       res = set_control(dev, CONTROL_FILTER_ORIGINAL |
+                              CONTROL_MODE_OSD |
+                              CONTROL_ENABLE_OFF);
+       if (res) {
+               debug("%s: Could not set control register (err = %d)\n",
+                     dev->name, res);
+               return res;
+       }
+
+       priv->base_width = ((features & BASE_WIDTH_MASK)
+                           >> BASE_WIDTH_SHIFT) + 1;
+       priv->base_height = ((features & BASE_HEIGTH_MASK)
+                            >> BASE_HEIGTH_SHIFT) + 1;
+
+       res = dev_read_phandle_with_args(dev, "clk_gen", NULL, 0, 0,
+                                        &phandle_args);
+       if (res) {
+               debug("%s: Could not get clk_gen node (err = %d)\n",
+                     dev->name, res);
+               return -EINVAL;
+       }
+
+       res = uclass_get_device_by_ofnode(UCLASS_CLK, phandle_args.node,
+                                         &priv->clk_gen);
+       if (res) {
+               debug("%s: Could not get clk_gen dev (err = %d)\n",
+                     dev->name, res);
+               return -EINVAL;
+       }
+
+       res = dev_read_phandle_with_args(dev, "video_tx", NULL, 0, 0,
+                                        &phandle_args);
+       if (res) {
+               debug("%s: Could not get video_tx (err = %d)\n",
+                     dev->name, res);
+               return -EINVAL;
+       }
+
+       res = uclass_get_device_by_ofnode(UCLASS_DISPLAY, phandle_args.node,
+                                         &priv->video_tx);
+       if (res) {
+               debug("%s: Could not get video_tx dev (err = %d)\n",
+                     dev->name, res);
+               return -EINVAL;
+       }
+
+       res = display_enable(priv->video_tx, 8, &timing);
+       if (res) {
+               debug("%s: Could not enable the display (err = %d)\n",
+                     dev->name, res);
+               return res;
+       }
+
+       return 0;
+}
+
+U_BOOT_DRIVER(ihs_video_out_drv) = {
+       .name           = "ihs_video_out_drv",
+       .id             = UCLASS_VIDEO_OSD,
+       .ops            = &ihs_video_out_ops,
+       .of_match       = ihs_video_out_ids,
+       .probe          = ihs_video_out_probe,
+       .priv_auto_alloc_size = sizeof(struct ihs_video_out_priv),
+};