// 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 #include #include #include #include #include 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 not 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 && res != -EIO) { /* Ignore missing DP sink error */ 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), };