mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-10 07:04:28 +00:00
video_osd: Add ihs_video_out driver
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>
This commit is contained in:
parent
39a336f116
commit
9671f696e5
4 changed files with 374 additions and 0 deletions
23
doc/device-tree-bindings/video/osd/gdsys,ihs_video_out.txt
Normal file
23
doc/device-tree-bindings/video/osd/gdsys,ihs_video_out.txt
Normal file
|
@ -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>;
|
||||
};
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
341
drivers/video/ihs_video_out.c
Normal file
341
drivers/video/ihs_video_out.c
Normal file
|
@ -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),
|
||||
};
|
Loading…
Reference in a new issue