Signed-off-by: Tom Rini <trini@konsulko.com>lime2-spi
commit
b592936d35
@ -0,0 +1,146 @@ |
||||
// 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, eibach@gdsys.de |
||||
*/ |
||||
|
||||
#include <common.h> |
||||
#include <dm.h> |
||||
#include <hexdump.h> |
||||
#include <video_osd.h> |
||||
#include <malloc.h> |
||||
|
||||
static int do_osd_write(cmd_tbl_t *cmdtp, int flag, int argc, |
||||
char * const argv[]) |
||||
{ |
||||
struct udevice *dev; |
||||
uint x, y; |
||||
uint count; |
||||
char *hexstr; |
||||
u8 *buffer; |
||||
size_t buflen; |
||||
int res; |
||||
|
||||
if (argc < 4 || (strlen(argv[3])) % 2) |
||||
return CMD_RET_USAGE; |
||||
|
||||
x = simple_strtoul(argv[1], NULL, 16); |
||||
y = simple_strtoul(argv[2], NULL, 16); |
||||
hexstr = argv[3]; |
||||
count = (argc > 4) ? simple_strtoul(argv[4], NULL, 16) : 1; |
||||
|
||||
buflen = strlen(hexstr) / 2; |
||||
|
||||
buffer = malloc(buflen); |
||||
if (!buffer) { |
||||
puts("Memory allocation failure\n"); |
||||
return CMD_RET_FAILURE; |
||||
} |
||||
|
||||
res = hex2bin(buffer, hexstr, buflen); |
||||
if (res) { |
||||
free(buffer); |
||||
puts("Hexadecimal input contained invalid characters\n"); |
||||
return CMD_RET_FAILURE; |
||||
} |
||||
|
||||
for (uclass_first_device(UCLASS_VIDEO_OSD, &dev); |
||||
dev; |
||||
uclass_next_device(&dev)) { |
||||
int res; |
||||
|
||||
res = video_osd_set_mem(dev, x, y, buffer, buflen, count); |
||||
if (res) { |
||||
free(buffer); |
||||
printf("Could not write to video mem on osd %s\n", |
||||
dev->name); |
||||
return CMD_RET_FAILURE; |
||||
} |
||||
} |
||||
|
||||
free(buffer); |
||||
|
||||
return CMD_RET_SUCCESS; |
||||
} |
||||
|
||||
static int do_osd_print(cmd_tbl_t *cmdtp, int flag, int argc, |
||||
char * const argv[]) |
||||
{ |
||||
struct udevice *dev; |
||||
uint x, y; |
||||
u8 color; |
||||
char *text; |
||||
|
||||
if (argc < 5) |
||||
return CMD_RET_USAGE; |
||||
|
||||
x = simple_strtoul(argv[1], NULL, 16); |
||||
y = simple_strtoul(argv[2], NULL, 16); |
||||
color = simple_strtoul(argv[3], NULL, 16); |
||||
text = argv[4]; |
||||
|
||||
for (uclass_first_device(UCLASS_VIDEO_OSD, &dev); |
||||
dev; |
||||
uclass_next_device(&dev)) { |
||||
int res; |
||||
|
||||
res = video_osd_print(dev, x, y, color, text); |
||||
if (res) { |
||||
printf("Could not print string to osd %s\n", dev->name); |
||||
return CMD_RET_FAILURE; |
||||
} |
||||
} |
||||
|
||||
return CMD_RET_SUCCESS; |
||||
} |
||||
|
||||
static int do_osd_size(cmd_tbl_t *cmdtp, int flag, int argc, |
||||
char * const argv[]) |
||||
{ |
||||
struct udevice *dev; |
||||
uint x, y; |
||||
|
||||
if (argc < 3) |
||||
return CMD_RET_USAGE; |
||||
|
||||
x = simple_strtoul(argv[1], NULL, 16); |
||||
y = simple_strtoul(argv[2], NULL, 16); |
||||
|
||||
for (uclass_first_device(UCLASS_VIDEO_OSD, &dev); |
||||
dev; |
||||
uclass_next_device(&dev)) { |
||||
int res; |
||||
|
||||
res = video_osd_set_size(dev, x, y); |
||||
|
||||
if (res) { |
||||
printf("Could not set size on osd %s\n", dev->name); |
||||
return CMD_RET_FAILURE; |
||||
} |
||||
} |
||||
|
||||
return CMD_RET_SUCCESS; |
||||
} |
||||
|
||||
U_BOOT_CMD( |
||||
osdw, 5, 0, do_osd_write, |
||||
"write 16-bit hex encoded buffer to osd memory", |
||||
"osdw [pos_x] [pos_y] [buffer] [count] - write 8-bit hex encoded buffer to osd memory\n" |
||||
); |
||||
|
||||
U_BOOT_CMD( |
||||
osdp, 5, 0, do_osd_print, |
||||
"write ASCII buffer to osd memory", |
||||
"osdp [pos_x] [pos_y] [color] [text] - write ASCII buffer to osd memory\n" |
||||
); |
||||
|
||||
U_BOOT_CMD( |
||||
osdsize, 3, 0, do_osd_size, |
||||
"set OSD XY size in characters", |
||||
"osdsize [size_x] [size_y] - set OSD XY size in characters\n" |
||||
); |
@ -0,0 +1,291 @@ |
||||
// 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, eibach@gdsys.de |
||||
*/ |
||||
|
||||
#include <common.h> |
||||
#include <dm.h> |
||||
#include <hexdump.h> |
||||
#include <video_osd.h> |
||||
#include <malloc.h> |
||||
|
||||
/* Container for selected OSD device */ |
||||
static struct udevice *osd_cur; |
||||
|
||||
/**
|
||||
* cmd_osd_set_osd_num() - Set the OSD selected for operation |
||||
* |
||||
* Set the OSD device, which will be used by all subsequent OSD commands. |
||||
* |
||||
* Devices are identified by their uclass sequence number (as listed by 'osd |
||||
* show'). |
||||
* |
||||
* @osdnum: The OSD device to be selected, identified by its sequence number. |
||||
* Return: 0 if OK, -ve on error |
||||
*/ |
||||
static int cmd_osd_set_osd_num(unsigned int osdnum) |
||||
{ |
||||
struct udevice *osd; |
||||
int res; |
||||
|
||||
res = uclass_get_device_by_seq(UCLASS_VIDEO_OSD, osdnum, &osd); |
||||
if (res) { |
||||
printf("%s: No OSD %u (err = %d)\n", __func__, osdnum, res); |
||||
return res; |
||||
} |
||||
osd_cur = osd; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
/**
|
||||
* osd_get_osd_cur() - Get the selected OSD device |
||||
* |
||||
* Get the OSD device that is used by all OSD commands. |
||||
* |
||||
* @osdp: Pointer to structure that will receive the currently selected OSD |
||||
* device. |
||||
* Return: 0 if OK, -ve on error |
||||
*/ |
||||
static int osd_get_osd_cur(struct udevice **osdp) |
||||
{ |
||||
if (!osd_cur) { |
||||
puts("No osd selected\n"); |
||||
return -ENODEV; |
||||
} |
||||
*osdp = osd_cur; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
/**
|
||||
* show_osd() - Display information about a OSD device |
||||
* |
||||
* Display a device's ID (sequence number), and whether it is active (i.e. |
||||
* probed) or not. |
||||
* |
||||
* @osd: OSD device to print information for |
||||
*/ |
||||
static void show_osd(struct udevice *osd) |
||||
{ |
||||
printf("OSD %d:\t%s", osd->req_seq, osd->name); |
||||
if (device_active(osd)) |
||||
printf(" (active %d)", osd->seq); |
||||
printf("\n"); |
||||
} |
||||
|
||||
static int do_osd_write(cmd_tbl_t *cmdtp, int flag, int argc, |
||||
char * const argv[]) |
||||
{ |
||||
uint x, y; |
||||
uint count; |
||||
char *hexstr; |
||||
u8 *buffer; |
||||
size_t buflen; |
||||
int res; |
||||
|
||||
if (argc < 4 || (strlen(argv[3]) % 2)) |
||||
return CMD_RET_USAGE; |
||||
|
||||
if (!osd_cur) { |
||||
puts("No osd selected\n"); |
||||
return CMD_RET_FAILURE; |
||||
} |
||||
|
||||
x = simple_strtoul(argv[1], NULL, 16); |
||||
y = simple_strtoul(argv[2], NULL, 16); |
||||
hexstr = argv[3]; |
||||
count = (argc > 4) ? simple_strtoul(argv[4], NULL, 16) : 1; |
||||
|
||||
buflen = strlen(hexstr) / 2; |
||||
|
||||
buffer = malloc(buflen); |
||||
if (!buffer) { |
||||
puts("Memory allocation failure\n"); |
||||
return CMD_RET_FAILURE; |
||||
} |
||||
|
||||
res = hex2bin(buffer, hexstr, buflen); |
||||
if (res) { |
||||
free(buffer); |
||||
puts("Hexadecimal input contained invalid characters\n"); |
||||
return CMD_RET_FAILURE; |
||||
} |
||||
|
||||
res = video_osd_set_mem(osd_cur, x, y, buffer, buflen, count); |
||||
if (res) { |
||||
free(buffer); |
||||
printf("%s: Could not write to video mem\n", |
||||
osd_cur->name); |
||||
return CMD_RET_FAILURE; |
||||
} |
||||
|
||||
free(buffer); |
||||
|
||||
return CMD_RET_SUCCESS; |
||||
} |
||||
|
||||
static int do_osd_print(cmd_tbl_t *cmdtp, int flag, int argc, |
||||
char * const argv[]) |
||||
{ |
||||
uint x, y; |
||||
u8 color; |
||||
char *text; |
||||
int res; |
||||
|
||||
if (argc < 5) |
||||
return CMD_RET_USAGE; |
||||
|
||||
if (!osd_cur) { |
||||
puts("No osd selected\n"); |
||||
return CMD_RET_FAILURE; |
||||
} |
||||
|
||||
x = simple_strtoul(argv[1], NULL, 16); |
||||
y = simple_strtoul(argv[2], NULL, 16); |
||||
color = simple_strtoul(argv[3], NULL, 16); |
||||
text = argv[4]; |
||||
|
||||
res = video_osd_print(osd_cur, x, y, color, text); |
||||
if (res) { |
||||
printf("Could not print string to osd %s\n", osd_cur->name); |
||||
return CMD_RET_FAILURE; |
||||
} |
||||
|
||||
return CMD_RET_SUCCESS; |
||||
} |
||||
|
||||
static int do_osd_size(cmd_tbl_t *cmdtp, int flag, int argc, |
||||
char * const argv[]) |
||||
{ |
||||
uint x, y; |
||||
int res; |
||||
|
||||
if (argc < 3) |
||||
return CMD_RET_USAGE; |
||||
|
||||
if (!osd_cur) { |
||||
puts("No osd selected\n"); |
||||
return CMD_RET_FAILURE; |
||||
} |
||||
|
||||
x = simple_strtoul(argv[1], NULL, 16); |
||||
y = simple_strtoul(argv[2], NULL, 16); |
||||
|
||||
res = video_osd_set_size(osd_cur, x, y); |
||||
if (res) { |
||||
printf("Could not set size on osd %s\n", osd_cur->name); |
||||
return CMD_RET_FAILURE; |
||||
} |
||||
|
||||
return CMD_RET_SUCCESS; |
||||
} |
||||
|
||||
static int do_show_osd(cmd_tbl_t *cmdtp, int flag, int argc, |
||||
char * const argv[]) |
||||
{ |
||||
struct udevice *osd; |
||||
|
||||
if (argc == 1) { |
||||
/* show all OSDs */ |
||||
struct uclass *uc; |
||||
int res; |
||||
|
||||
res = uclass_get(UCLASS_VIDEO_OSD, &uc); |
||||
if (res) { |
||||
printf("Error while getting OSD uclass (err=%d)\n", |
||||
res); |
||||
return CMD_RET_FAILURE; |
||||
} |
||||
|
||||
uclass_foreach_dev(osd, uc) |
||||
show_osd(osd); |
||||
} else { |
||||
int i, res; |
||||
|
||||
/* show specific OSD */ |
||||
i = simple_strtoul(argv[1], NULL, 10); |
||||
|
||||
res = uclass_get_device_by_seq(UCLASS_VIDEO_OSD, i, &osd); |
||||
if (res) { |
||||
printf("Invalid osd %d: err=%d\n", i, res); |
||||
return CMD_RET_FAILURE; |
||||
} |
||||
show_osd(osd); |
||||
} |
||||
|
||||
return CMD_RET_SUCCESS; |
||||
} |
||||
|
||||
static int do_osd_num(cmd_tbl_t *cmdtp, int flag, int argc, |
||||
char * const argv[]) |
||||
{ |
||||
int osd_no; |
||||
int res = 0; |
||||
|
||||
if (argc == 1) { |
||||
/* querying current setting */ |
||||
struct udevice *osd; |
||||
|
||||
if (!osd_get_osd_cur(&osd)) |
||||
osd_no = osd->seq; |
||||
else |
||||
osd_no = -1; |
||||
printf("Current osd is %d\n", osd_no); |
||||
} else { |
||||
osd_no = simple_strtoul(argv[1], NULL, 10); |
||||
printf("Setting osd to %d\n", osd_no); |
||||
|
||||
res = cmd_osd_set_osd_num(osd_no); |
||||
if (res) |
||||
printf("Failure changing osd number (err = %d)\n", res); |
||||
} |
||||
|
||||
return res ? CMD_RET_FAILURE : CMD_RET_SUCCESS; |
||||
} |
||||
|
||||
static cmd_tbl_t cmd_osd_sub[] = { |
||||
U_BOOT_CMD_MKENT(show, 1, 1, do_show_osd, "", ""), |
||||
U_BOOT_CMD_MKENT(dev, 1, 1, do_osd_num, "", ""), |
||||
U_BOOT_CMD_MKENT(write, 4, 1, do_osd_write, "", ""), |
||||
U_BOOT_CMD_MKENT(print, 4, 1, do_osd_print, "", ""), |
||||
U_BOOT_CMD_MKENT(size, 2, 1, do_osd_size, "", ""), |
||||
}; |
||||
|
||||
static int do_osd(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) |
||||
{ |
||||
cmd_tbl_t *c; |
||||
|
||||
if (argc < 2) |
||||
return CMD_RET_USAGE; |
||||
|
||||
/* Strip off leading 'osd' command argument */ |
||||
argc--; |
||||
argv++; |
||||
|
||||
c = find_cmd_tbl(argv[0], &cmd_osd_sub[0], ARRAY_SIZE(cmd_osd_sub)); |
||||
|
||||
if (c) |
||||
return c->cmd(cmdtp, flag, argc, argv); |
||||
else |
||||
return CMD_RET_USAGE; |
||||
} |
||||
|
||||
static char osd_help_text[] = |
||||
"show - show OSD info\n" |
||||
"osd dev [dev] - show or set current OSD\n" |
||||
"write [pos_x] [pos_y] [buffer] [count] - write 8-bit hex encoded buffer to osd memory at a given position\n" |
||||
"print [pos_x] [pos_y] [color] [text] - write ASCII buffer (given by text data and driver-specific color information) to osd memory\n" |
||||
"size [size_x] [size_y] - set OSD XY size in characters\n"; |
||||
|
||||
U_BOOT_CMD( |
||||
osd, 6, 1, do_osd, |
||||
"OSD sub-system", |
||||
osd_help_text |
||||
); |
@ -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>; |
||||
}; |
@ -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), |
||||
}; |
@ -0,0 +1,405 @@ |
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* (C) Copyright 2016-2018 ARM Ltd. |
||||
* Author: Liviu Dudau <liviu.dudau@foss.arm.com> |
||||
* |
||||
*/ |
||||
#define DEBUG |
||||
#include <common.h> |
||||
#include <video.h> |
||||
#include <dm.h> |
||||
#ifdef CONFIG_DISPLAY |
||||
#include <display.h> |
||||
#endif |
||||
#include <fdtdec.h> |
||||
#include <asm/io.h> |
||||
#include <os.h> |
||||
#include <fdt_support.h> |
||||
#include <clk.h> |
||||
#include <linux/sizes.h> |
||||
|
||||
#define MALIDP_CORE_ID 0x0018 |
||||
#define MALIDP_REG_BG_COLOR 0x0044 |
||||
#define MALIDP_LAYER_LV1 0x0100 |
||||
#define MALIDP_DC_STATUS 0xc000 |
||||
#define MALIDP_DC_CONTROL 0xc010 |
||||
#define MALIDP_DC_CFG_VALID 0xc014 |
||||
|
||||
/* offsets inside the modesetting register block */ |
||||
#define MALIDP_H_INTERVALS 0x0000 |
||||
#define MALIDP_V_INTERVALS 0x0004 |
||||
#define MALIDP_SYNC_CONTROL 0x0008 |
||||
#define MALIDP_HV_ACTIVESIZE 0x000c |
||||
#define MALIDP_OUTPUT_DEPTH 0x001c |
||||
|
||||
/* offsets inside the layer register block */ |
||||
#define MALIDP_LAYER_FORMAT 0x0000 |
||||
#define MALIDP_LAYER_CONTROL 0x0004 |
||||
#define MALIDP_LAYER_IN_SIZE 0x000c |
||||
#define MALIDP_LAYER_CMP_SIZE 0x0010 |
||||
#define MALIDP_LAYER_STRIDE 0x0018 |
||||
#define MALIDP_LAYER_PTR_LOW 0x0024 |
||||
#define MALIDP_LAYER_PTR_HIGH 0x0028 |
||||
|
||||
/* offsets inside the IRQ control blocks */ |
||||
#define MALIDP_REG_MASKIRQ 0x0008 |
||||
#define MALIDP_REG_CLEARIRQ 0x000c |
||||
|
||||
#define M1BITS 0x0001 |
||||
#define M2BITS 0x0003 |
||||
#define M4BITS 0x000f |
||||
#define M8BITS 0x00ff |
||||
#define M10BITS 0x03ff |
||||
#define M12BITS 0x0fff |
||||
#define M13BITS 0x1fff |
||||
#define M16BITS 0xffff |
||||
#define M17BITS 0x1ffff |
||||
|
||||
#define MALIDP_H_FRONTPORCH(x) (((x) & M12BITS) << 0) |
||||
#define MALIDP_H_BACKPORCH(x) (((x) & M10BITS) << 16) |
||||
#define MALIDP_V_FRONTPORCH(x) (((x) & M12BITS) << 0) |
||||
#define MALIDP_V_BACKPORCH(x) (((x) & M8BITS) << 16) |
||||
#define MALIDP_H_SYNCWIDTH(x) (((x) & M10BITS) << 0) |
||||
#define MALIDP_V_SYNCWIDTH(x) (((x) & M8BITS) << 16) |
||||
#define MALIDP_H_ACTIVE(x) (((x) & M13BITS) << 0) |
||||
#define MALIDP_V_ACTIVE(x) (((x) & M13BITS) << 16) |
||||
|
||||
#define MALIDP_CMP_V_SIZE(x) (((x) & M13BITS) << 16) |
||||
#define MALIDP_CMP_H_SIZE(x) (((x) & M13BITS) << 0) |
||||
|
||||
#define MALIDP_IN_V_SIZE(x) (((x) & M13BITS) << 16) |
||||
#define MALIDP_IN_H_SIZE(x) (((x) & M13BITS) << 0) |
||||
|
||||
#define MALIDP_DC_CM_CONTROL(x) ((x) & M1BITS) << 16, 1 << 16 |
||||
#define MALIDP_DC_STATUS_GET_CM(reg) (((reg) >> 16) & M1BITS) |
||||
|
||||
#define MALIDP_FORMAT_ARGB8888 0x08 |
||||
#define MALIDP_DEFAULT_BG_R 0x0 |
||||
#define MALIDP_DEFAULT_BG_G 0x0 |
||||
#define MALIDP_DEFAULT_BG_B 0x0 |
||||
|
||||
#define MALIDP_PRODUCT_ID(core_id) ((u32)(core_id) >> 16) |
||||
|
||||
#define MALIDP500 0x500 |
||||
|
||||
DECLARE_GLOBAL_DATA_PTR; |
||||
|
||||
struct malidp_priv { |
||||
phys_addr_t base_addr; |
||||
phys_addr_t dc_status_addr; |
||||
phys_addr_t dc_control_addr; |
||||
phys_addr_t cval_addr; |
||||
struct udevice *display; /* display device attached */ |
||||
struct clk aclk; |
||||
struct clk pxlclk; |
||||
u16 modeset_regs_offset; |
||||
u8 config_bit_shift; |
||||
u8 clear_irq; /* offset for IRQ clear register */ |
||||
}; |
||||
|
||||
static const struct video_ops malidp_ops = { |
||||
}; |
||||
|
||||
static int malidp_get_hwid(phys_addr_t base_addr) |
||||
{ |
||||
int hwid; |
||||
|
||||
/*
|
||||
* reading from the old CORE_ID offset will always |
||||
* return 0x5000000 on DP500 |
||||
*/ |
||||
hwid = readl(base_addr + MALIDP_CORE_ID); |
||||
if (MALIDP_PRODUCT_ID(hwid) == MALIDP500) |
||||
return hwid; |
||||
/* otherwise try the other gen CORE_ID offset */ |
||||
hwid = readl(base_addr + MALIDP_DC_STATUS + MALIDP_CORE_ID); |
||||
|
||||
return hwid; |
||||
} |
||||
|
||||
/*
|
||||
* wait for config mode bit setup to be acted upon by the hardware |
||||
*/ |
||||
static int malidp_wait_configdone(struct malidp_priv *malidp) |
||||
{ |
||||
u32 status, tries = 300; |
||||
|
||||
while (tries--) { |
||||
status = readl(malidp->dc_status_addr); |
||||
if ((status >> malidp->config_bit_shift) & 1) |
||||
break; |
||||
udelay(500); |
||||
} |
||||
|
||||
if (!tries) |
||||
return -ETIMEDOUT; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
/*
|
||||
* signal the hardware to enter configuration mode |
||||
*/ |
||||
static int malidp_enter_config(struct malidp_priv *malidp) |
||||
{ |
||||
setbits_le32(malidp->dc_control_addr, 1 << malidp->config_bit_shift); |
||||
return malidp_wait_configdone(malidp); |
||||
} |
||||
|
||||
/*
|
||||
* signal the hardware to exit configuration mode |
||||
*/ |
||||
static int malidp_leave_config(struct malidp_priv *malidp) |
||||
{ |
||||
clrbits_le32(malidp->dc_control_addr, 1 << malidp->config_bit_shift); |
||||
return malidp_wait_configdone(malidp); |
||||
} |
||||
|
||||
static void malidp_setup_timings(struct malidp_priv *malidp, |
||||
struct display_timing *timings) |
||||
{ |
||||
u32 val = MALIDP_H_SYNCWIDTH(timings->hsync_len.typ) | |
||||
MALIDP_V_SYNCWIDTH(timings->vsync_len.typ); |
||||
writel(val, malidp->base_addr + malidp->modeset_regs_offset + |
||||
MALIDP_SYNC_CONTROL); |
||||
val = MALIDP_H_BACKPORCH(timings->hback_porch.typ) | |
||||
MALIDP_H_FRONTPORCH(timings->hfront_porch.typ); |
||||
writel(val, malidp->base_addr + malidp->modeset_regs_offset + |
||||
MALIDP_H_INTERVALS); |
||||
val = MALIDP_V_BACKPORCH(timings->vback_porch.typ) | |
||||
MALIDP_V_FRONTPORCH(timings->vfront_porch.typ); |
||||
writel(val, malidp->base_addr + malidp->modeset_regs_offset + |
||||
MALIDP_V_INTERVALS); |
||||
val = MALIDP_H_ACTIVE(timings->hactive.typ) | |
||||
MALIDP_V_ACTIVE(timings->vactive.typ); |
||||
writel(val, malidp->base_addr + malidp->modeset_regs_offset + |
||||
MALIDP_HV_ACTIVESIZE); |
||||
/* default output bit-depth per colour is 8 bits */ |
||||
writel(0x080808, malidp->base_addr + malidp->modeset_regs_offset + |
||||
MALIDP_OUTPUT_DEPTH); |
||||
} |
||||
|
||||
static int malidp_setup_mode(struct malidp_priv *malidp, |
||||
struct display_timing *timings) |
||||
{ |
||||
int err; |
||||
|
||||
if (clk_set_rate(&malidp->pxlclk, timings->pixelclock.typ) == 0) |
||||
return -EIO; |
||||
|
||||
malidp_setup_timings(malidp, timings); |
||||
|
||||
err = display_enable(malidp->display, 8, timings); |
||||
if (err) |
||||
printf("display_enable failed with %d\n", err); |
||||
|
||||
return err; |
||||
} |
||||
|
||||
static void malidp_setup_layer(struct malidp_priv *malidp, |
||||
struct display_timing *timings, |
||||
u32 layer_offset, phys_addr_t fb_addr) |
||||
{ |
||||
u32 val; |
||||
|
||||
/* setup the base layer's pixel format to A8R8G8B8 */ |
||||
writel(MALIDP_FORMAT_ARGB8888, malidp->base_addr + layer_offset + |
||||
MALIDP_LAYER_FORMAT); |
||||
/* setup layer composition size */ |
||||
val = MALIDP_CMP_V_SIZE(timings->vactive.typ) | |
||||
MALIDP_CMP_H_SIZE(timings->hactive.typ); |
||||
writel(val, malidp->base_addr + layer_offset + |
||||
MALIDP_LAYER_CMP_SIZE); |
||||
/* setup layer input size */ |
||||
val = MALIDP_IN_V_SIZE(timings->vactive.typ) | |
||||
MALIDP_IN_H_SIZE(timings->hactive.typ); |
||||
writel(val, malidp->base_addr + layer_offset + MALIDP_LAYER_IN_SIZE); |
||||
/* setup layer stride in bytes */ |
||||
writel(timings->hactive.typ << 2, malidp->base_addr + layer_offset + |
||||
MALIDP_LAYER_STRIDE); |
||||
/* set framebuffer address */ |
||||
writel(lower_32_bits(fb_addr), malidp->base_addr + layer_offset + |
||||
MALIDP_LAYER_PTR_LOW); |
||||
writel(upper_32_bits(fb_addr), malidp->base_addr + layer_offset + |
||||
MALIDP_LAYER_PTR_HIGH); |
||||
/* enable layer */ |
||||
setbits_le32(malidp->base_addr + layer_offset + |
||||
MALIDP_LAYER_CONTROL, 1); |
||||
} |
||||
|
||||
static void malidp_set_configvalid(struct malidp_priv *malidp) |
||||
{ |
||||
setbits_le32(malidp->cval_addr, 1); |
||||
} |
||||
|
||||
static int malidp_update_timings_from_edid(struct udevice *dev, |
||||
struct display_timing *timings) |
||||
{ |
||||
#ifdef CONFIG_DISPLAY |
||||
struct malidp_priv *priv = dev_get_priv(dev); |
||||
struct udevice *disp_dev; |
||||
int err; |
||||
|
||||
err = uclass_first_device(UCLASS_DISPLAY, &disp_dev); |
||||
if (err) |
||||
return err; |
||||
|
||||
priv->display = disp_dev; |
||||
|
||||
err = display_read_timing(disp_dev, timings); |
||||
if (err) |
||||
return err; |
||||
|
||||
#endif |
||||
return 0; |
||||
} |
||||
|
||||
static int malidp_probe(struct udevice *dev) |
||||
{ |
||||
struct video_priv *uc_priv = dev_get_uclass_priv(dev); |
||||
struct video_uc_platdata *uc_plat = dev_get_uclass_platdata(dev); |
||||
ofnode framebuffer = ofnode_find_subnode(dev_ofnode(dev), "framebuffer"); |
||||
struct malidp_priv *priv = dev_get_priv(dev); |
||||
struct display_timing timings; |
||||
phys_addr_t fb_base, fb_size; |
||||
const char *format; |
||||
u32 value; |
||||
int err; |
||||
|
||||
if (!ofnode_valid(framebuffer)) |
||||
return -EINVAL; |
||||
|
||||
err = clk_get_by_name(dev, "pxlclk", &priv->pxlclk); |
||||
if (err) { |
||||
dev_err(dev, "failed to get pixel clock\n"); |
||||
return err; |
||||
} |
||||
err = clk_get_by_name(dev, "aclk", &priv->aclk); |
||||
if (err) { |
||||
dev_err(dev, "failed to get AXI clock\n"); |
||||
goto fail_aclk; |
||||
} |
||||
|
||||
err = ofnode_decode_display_timing(dev_ofnode(dev), 1, &timings); |
||||
if (err) { |
||||
dev_err(dev, "failed to get any display timings\n"); |
||||
goto fail_timings; |
||||
} |
||||
|
||||
err = malidp_update_timings_from_edid(dev, &timings); |
||||
if (err) { |
||||
printf("malidp_update_timings_from_edid failed: %d\n", err); |
||||
goto fail_timings; |
||||
} |
||||
|
||||
fb_base = ofnode_get_addr_size(framebuffer, "reg", &fb_size); |
||||
if (fb_base != FDT_ADDR_T_NONE) { |
||||
uc_plat->base = fb_base; |
||||
uc_plat->size = fb_size; |
||||
} else { |
||||
printf("cannot get address size for framebuffer\n"); |
||||
} |
||||
|
||||
err = ofnode_read_u32(framebuffer, "width", &value); |
||||
if (err) |
||||
goto fail_timings; |
||||
uc_priv->xsize = (ushort)value; |
||||
|
||||
err = ofnode_read_u32(framebuffer, "height", &value); |
||||
if (err) |
||||
goto fail_timings; |
||||
uc_priv->ysize = (ushort)value; |
||||
|
||||
format = ofnode_read_string(framebuffer, "format"); |
||||
if (!format) { |
||||
err = -EINVAL; |
||||
goto fail_timings; |
||||
} else if (!strncmp(format, "a8r8g8b8", 8)) { |
||||
uc_priv->bpix = VIDEO_BPP32; |
||||
} |
||||
|
||||
uc_priv->rot = 0; |
||||
priv->base_addr = (phys_addr_t)dev_read_addr(dev); |
||||
|
||||
clk_enable(&priv->pxlclk); |
||||
clk_enable(&priv->aclk); |
||||
|
||||
value = malidp_get_hwid(priv->base_addr); |
||||
printf("Display: Arm Mali DP%3x r%dp%d\n", MALIDP_PRODUCT_ID(value), |
||||
(value >> 12) & 0xf, (value >> 8) & 0xf); |
||||
|
||||
if (MALIDP_PRODUCT_ID(value) == MALIDP500) { |
||||
/* DP500 is special */ |
||||
priv->modeset_regs_offset = 0x28; |
||||
priv->dc_status_addr = priv->base_addr; |
||||
priv->dc_control_addr = priv->base_addr + 0xc; |
||||
priv->cval_addr = priv->base_addr + 0xf00; |
||||
priv->config_bit_shift = 17; |
||||
priv->clear_irq = 0; |
||||
} else { |
||||
priv->modeset_regs_offset = 0x30; |
||||
priv->dc_status_addr = priv->base_addr + MALIDP_DC_STATUS; |
||||
priv->dc_control_addr = priv->base_addr + MALIDP_DC_CONTROL; |
||||
priv->cval_addr = priv->base_addr + MALIDP_DC_CFG_VALID; |
||||
priv->config_bit_shift = 16; |
||||
priv->clear_irq = MALIDP_REG_CLEARIRQ; |
||||
} |
||||
|
||||
/* enter config mode */ |
||||
err = malidp_enter_config(priv); |
||||
if (err) |
||||
return err; |
||||
|
||||
/* disable interrupts */ |
||||
writel(0, priv->dc_status_addr + MALIDP_REG_MASKIRQ); |
||||
writel(0xffffffff, priv->dc_status_addr + priv->clear_irq); |
||||
|
||||
err = malidp_setup_mode(priv, &timings); |
||||
if (err) |
||||
goto fail_timings; |
||||
|
||||
malidp_setup_layer(priv, &timings, MALIDP_LAYER_LV1, |
||||
(phys_addr_t)uc_plat->base); |
||||
|
||||
err = malidp_leave_config(priv); |
||||
if (err) |
||||
goto fail_timings; |
||||
|
||||
malidp_set_configvalid(priv); |
||||
|
||||
return 0; |
||||
|
||||
fail_timings: |
||||
clk_free(&priv->aclk); |
||||
fail_aclk: |
||||
clk_free(&priv->pxlclk); |
||||
|
||||
return err; |
||||
} |
||||
|
||||
static int malidp_bind(struct udevice *dev) |
||||
{ |
||||
struct video_uc_platdata *uc_plat = dev_get_uclass_platdata(dev); |
||||
|
||||
/* choose max possible size: 2K x 2K, XRGB888 framebuffer */ |
||||
uc_plat->size = 4 * 2048 * 2048; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static const struct udevice_id malidp_ids[] = { |
||||
{ .compatible = "arm,mali-dp500" }, |
||||
{ .compatible = "arm,mali-dp550" }, |
||||
{ .compatible = "arm,mali-dp650" }, |
||||
{ } |
||||
}; |
||||
|
||||
U_BOOT_DRIVER(mali_dp) = { |
||||
.name = "mali_dp", |
||||
.id = UCLASS_VIDEO, |
||||
.of_match = malidp_ids, |
||||
.bind = malidp_bind, |
||||
.probe = malidp_probe, |
||||
.priv_auto_alloc_size = sizeof(struct malidp_priv), |
||||
.ops = &malidp_ops, |
||||
}; |
@ -0,0 +1,161 @@ |
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* (C) Copyright 2018 |
||||
* Mario Six, Guntermann & Drunck GmbH, mario.six@gdsys.cc |
||||
*/ |
||||
#include <common.h> |
||||
#include <display.h> |
||||
#include <dm.h> |
||||
#include <video_osd.h> |
||||
|
||||
#include "sandbox_osd.h" |
||||
|
||||
struct sandbox_osd_priv { |
||||
uint width; |
||||
uint height; |
||||
u16 *buf; |
||||
}; |
||||
|
||||
static const struct udevice_id sandbox_osd_ids[] = { |
||||
{ .compatible = "sandbox,sandbox_osd" }, |
||||
{ } |
||||
}; |
||||
|
||||
inline u16 make_memval(u8 chr, u8 color) |
||||
{ |
||||
return chr * 0x100 + color; |
||||
} |
||||
|
||||
int sandbox_osd_get_info(struct udevice *dev, struct video_osd_info *info) |
||||
{ |
||||
struct sandbox_osd_priv *priv = dev_get_priv(dev); |
||||
|
||||
info->width = priv->width; |
||||
info->height = priv->height; |
||||
info->major_version = 1; |
||||
info->minor_version = 0; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
int sandbox_osd_set_mem(struct udevice *dev, uint col, uint row, u8 *buf, |
||||
size_t buflen, uint count) |
||||
{ |
||||
struct sandbox_osd_priv *priv = dev_get_priv(dev); |
||||
int pos; |
||||
u8 *mem = (u8 *)priv->buf; |
||||
int i; |
||||
|
||||
pos = 2 * (row * priv->width + col); |
||||
|
||||
if (pos >= 2 * (priv->width * priv->height)) |
||||
return -EINVAL; |
||||
|
||||
for (i = 0; i < count; i++) |
||||
memcpy(mem + pos + (i * buflen), buf, buflen); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
int _sandbox_osd_set_size(struct udevice *dev, uint col, uint row) |
||||
{ |
||||
struct sandbox_osd_priv *priv = dev_get_priv(dev); |
||||
int i; |
||||
uint size; |
||||
|
||||
priv->width = col; |
||||
priv->height = row; |
||||
size = priv->width * priv->height; |
||||
if (!priv->buf) |
||||
priv->buf = calloc(size, sizeof(u16)); |
||||
else |
||||
priv->buf = realloc(priv->buf, size * sizeof(u16)); |
||||
|
||||
if (!priv->buf) |
||||
return -ENOMEM; |
||||
|
||||
/* Fill OSD with black spaces */ |
||||
for (i = 0; i < size; i++) |
||||
priv->buf[i] = make_memval(' ', 'k'); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
int sandbox_osd_set_size(struct udevice *dev, uint col, uint row) |
||||
{ |
||||
return _sandbox_osd_set_size(dev, col, row); |
||||
} |
||||
|
||||
int sandbox_osd_print(struct udevice *dev, uint col, uint row, ulong color, |
||||
char *text) |
||||
{ |
||||
struct sandbox_osd_priv *priv = dev_get_priv(dev); |
||||
char cval; |
||||
char *p; |
||||
int pos; |
||||
|
||||
if (col >= priv->width || row >= priv->height) |
||||
return -EINVAL; |
||||
|
||||
switch (color) { |
||||
case COLOR_BLACK: |
||||
cval = 'k'; |
||||
break; |
||||
case COLOR_WHITE: |
||||
cval = 'w'; |
||||
break; |
||||
case COLOR_RED: |
||||
cval = 'r'; |
||||
break; |
||||
case COLOR_GREEN: |
||||
cval = 'g'; |
||||
break; |
||||
case COLOR_BLUE: |
||||
cval = 'b'; |
||||
break; |
||||
default: |
||||
return -EINVAL; |
||||
} |
||||
|
||||
p = text; |
||||
pos = row * priv->width + col; |
||||
|
||||
while (*p) |
||||
priv->buf[pos++] = make_memval(*(p++), cval); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
int sandbox_osd_get_mem(struct udevice *dev, u8 *buf, size_t buflen) |
||||
{ |
||||
struct sandbox_osd_priv *priv = dev_get_priv(dev); |
||||
uint memsize = 2 * (priv->width * priv->height); |
||||
|
||||
if (buflen < memsize) |
||||
return -EINVAL; |
||||
|
||||
memcpy(buf, priv->buf, memsize); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static const struct video_osd_ops sandbox_osd_ops = { |
||||
.get_info = sandbox_osd_get_info, |
||||
.set_mem = sandbox_osd_set_mem, |
||||
.set_size = sandbox_osd_set_size, |
||||
.print = sandbox_osd_print, |
||||
}; |
||||
|
||||
int sandbox_osd_probe(struct udevice *dev) |
||||
{ |
||||
return _sandbox_osd_set_size(dev, 10, 10); |
||||
} |
||||
|
||||
U_BOOT_DRIVER(sandbox_osd_drv) = { |
||||
.name = "sandbox_osd_drv", |
||||
.id = UCLASS_VIDEO_OSD, |
||||
.ops = &sandbox_osd_ops, |
||||
.of_match = sandbox_osd_ids, |
||||
.probe = sandbox_osd_probe, |
||||
.priv_auto_alloc_size = sizeof(struct sandbox_osd_priv), |
||||
}; |
@ -0,0 +1,13 @@ |
||||
/* SPDX-License-Identifier: GPL-2.0+ */ |
||||
/*
|
||||
* (C) Copyright 2018 |
||||
* Mario Six, Guntermann & Drunck GmbH, mario.six@gdsys.cc |
||||
*/ |
||||
|
||||
enum { |
||||
COLOR_BLACK, |
||||
COLOR_WHITE, |
||||
COLOR_RED, |
||||
COLOR_GREEN, |
||||
COLOR_BLUE, |
||||
}; |
@ -0,0 +1,653 @@ |
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* (C) Copyright 2018 Liviu Dudau <liviu@dudau.co.uk> |
||||
* |
||||
* Based on the Linux driver, (C) 2012 Texas Instruments |
||||
*/ |
||||
|
||||
#include <common.h> |
||||
#include <dm.h> |
||||
#include <display.h> |
||||
#include <i2c.h> |
||||
|
||||
/*
|
||||
* TDA19988 uses paged registers. We encode the page# in the upper |
||||
* bits of the register#. It also means that reads/writes to a register |
||||
* have to ensure that the register's page is selected as the current |
||||
* page. |
||||
*/ |
||||
#define REG(page, addr) (((page) << 8) | (addr)) |
||||
#define REG2ADDR(reg) ((reg) & 0xff) |
||||
#define REG2PAGE(reg) (((reg) >> 8) & 0xff) |
||||
|
||||
/* register for setting current page */ |
||||
#define REG_CURRENT_PAGE 0xff |
||||
|
||||
/* Page 00h: General Control */ |
||||
#define REG_VERSION_LSB REG(0x00, 0x00) /* read */ |
||||
#define REG_MAIN_CNTRL0 REG(0x00, 0x01) /* read/write */ |
||||
#define MAIN_CNTRL0_SR BIT(0) |
||||
#define MAIN_CNTRL0_DECS BIT(1) |
||||
#define MAIN_CNTRL0_DEHS BIT(2) |
||||
#define MAIN_CNTRL0_CECS BIT(3) |
||||
#define MAIN_CNTRL0_CEHS BIT(4) |
||||
#define MAIN_CNTRL0_SCALER BIT(7) |
||||
#define REG_VERSION_MSB REG(0x00, 0x02) /* read */ |
||||
#define REG_SOFTRESET REG(0x00, 0x0a) /* write */ |
||||
#define SOFTRESET_AUDIO BIT(0) |
||||
#define SOFTRESET_I2C_MASTER BIT(1) |
||||
#define REG_DDC_DISABLE REG(0x00, 0x0b) /* read/write */ |
||||
#define REG_I2C_MASTER REG(0x00, 0x0d) /* read/write */ |
||||
#define I2C_MASTER_DIS_MM BIT(0) |
||||
#define I2C_MASTER_DIS_FILT BIT(1) |
||||
#define I2C_MASTER_APP_STRT_LAT BIT(2) |
||||
#define REG_FEAT_POWERDOWN REG(0x00, 0x0e) /* read/write */ |
||||
#define FEAT_POWERDOWN_PREFILT BIT(0) |
||||
#define FEAT_POWERDOWN_CSC BIT(1) |
||||
#define FEAT_POWERDOWN_SPDIF BIT(3) |
||||
#define REG_INT_FLAGS_0 REG(0x00, 0x0f) /* read/write */ |
||||
#define REG_INT_FLAGS_1 REG(0x00, 0x10) /* read/write */ |
||||
#define REG_INT_FLAGS_2 REG(0x00, 0x11) /* read/write */ |
||||
#define INT_FLAGS_2_EDID_BLK_RD BIT(1) |
||||
#define REG_ENA_VP_0 REG(0x00, 0x18) /* read/write */ |
||||
#define REG_ENA_VP_1 REG(0x00, 0x19) /* read/write */ |
||||
#define REG_ENA_VP_2 REG(0x00, 0x1a) /* read/write */ |
||||
#define REG_ENA_AP REG(0x00, 0x1e) /* read/write */ |
||||
#define REG_VIP_CNTRL_0 REG(0x00, 0x20) /* write */ |
||||
#define VIP_CNTRL_0_MIRR_A BIT(7) |
||||
#define VIP_CNTRL_0_SWAP_A(x) (((x) & 7) << 4) |
||||
#define VIP_CNTRL_0_MIRR_B BIT(3) |
||||
#define VIP_CNTRL_0_SWAP_B(x) (((x) & 7) << 0) |
||||
#define REG_VIP_CNTRL_1 REG(0x00, 0x21) /* write */ |
||||
#define VIP_CNTRL_1_MIRR_C BIT(7) |
||||
#define VIP_CNTRL_1_SWAP_C(x) (((x) & 7) << 4) |
||||
#define VIP_CNTRL_1_MIRR_D BIT(3) |
||||
#define VIP_CNTRL_1_SWAP_D(x) (((x) & 7) << 0) |
||||
#define REG_VIP_CNTRL_2 REG(0x00, 0x22) /* write */ |
||||
#define VIP_CNTRL_2_MIRR_E BIT(7) |
||||
#define VIP_CNTRL_2_SWAP_E(x) (((x) & 7) << 4) |
||||
#define VIP_CNTRL_2_MIRR_F BIT(3) |
||||
#define VIP_CNTRL_2_SWAP_F(x) (((x) & 7) << 0) |
||||
#define REG_VIP_CNTRL_3 REG(0x00, 0x23) /* write */ |
||||
#define VIP_CNTRL_3_X_TGL BIT(0) |
||||
#define VIP_CNTRL_3_H_TGL BIT(1) |
||||
#define VIP_CNTRL_3_V_TGL BIT(2) |
||||
#define VIP_CNTRL_3_EMB BIT(3) |
||||
#define VIP_CNTRL_3_SYNC_DE BIT(4) |
||||
#define VIP_CNTRL_3_SYNC_HS BIT(5) |
||||
#define VIP_CNTRL_3_DE_INT BIT(6) |
||||
#define VIP_CNTRL_3_EDGE BIT(7) |
||||
#define REG_VIP_CNTRL_4 REG(0x00, 0x24) /* write */ |
||||
#define VIP_CNTRL_4_BLC(x) (((x) & 3) << 0) |
||||
#define VIP_CNTRL_4_BLANKIT(x) (((x) & 3) << 2) |
||||
#define VIP_CNTRL_4_CCIR656 BIT(4) |
||||
#define VIP_CNTRL_4_656_ALT BIT(5) |
||||
#define VIP_CNTRL_4_TST_656 BIT(6) |
||||
#define VIP_CNTRL_4_TST_PAT BIT(7) |
||||
#define REG_VIP_CNTRL_5 REG(0x00, 0x25) /* write */ |
||||
#define VIP_CNTRL_5_CKCASE BIT(0) |
||||
#define VIP_CNTRL_5_SP_CNT(x) (((x) & 3) << 1) |
||||
#define REG_MUX_VP_VIP_OUT REG(0x00, 0x27) /* read/write */ |
||||
#define REG_MAT_CONTRL REG(0x00, 0x80) /* write */ |
||||
#define MAT_CONTRL_MAT_SC(x) (((x) & 3) << 0) |
||||
#define MAT_CONTRL_MAT_BP BIT(2) |
||||
#define REG_VIDFORMAT REG(0x00, 0xa0) /* write */ |
||||
#define REG_REFPIX_MSB REG(0x00, 0xa1) /* write */ |
||||
#define REG_REFPIX_LSB REG(0x00, 0xa2) /* write */ |
||||
#define REG_REFLINE_MSB REG(0x00, 0xa3) /* write */ |
||||
#define REG_REFLINE_LSB REG(0x00, 0xa4) /* write */ |
||||
#define REG_NPIX_MSB REG(0x00, 0xa5) /* write */ |
||||
#define REG_NPIX_LSB REG(0x00, 0xa6) /* write */ |
||||
#define REG_NLINE_MSB REG(0x00, 0xa7) /* write */ |
||||
#define REG_NLINE_LSB REG(0x00, 0xa8) /* write */ |
||||
#define REG_VS_LINE_STRT_1_MSB REG(0x00, 0xa9) /* write */ |
||||
#define REG_VS_LINE_STRT_1_LSB REG(0x00, 0xaa) /* write */ |
||||
#define REG_VS_PIX_STRT_1_MSB REG(0x00, 0xab) /* write */ |
||||
#define REG_VS_PIX_STRT_1_LSB REG(0x00, 0xac) /* write */ |
||||
#define REG_VS_LINE_END_1_MSB REG(0x00, 0xad) /* write */ |
||||
#define REG_VS_LINE_END_1_LSB REG(0x00, 0xae) /* write */ |
||||
#define REG_VS_PIX_END_1_MSB REG(0x00, 0xaf) /* write */ |
||||
#define REG_VS_PIX_END_1_LSB REG(0x00, 0xb0) /* write */ |
||||
#define REG_VS_LINE_STRT_2_MSB REG(0x00, 0xb1) /* write */ |
||||
#define REG_VS_LINE_STRT_2_LSB REG(0x00, 0xb2) /* write */ |
||||
#define REG_VS_PIX_STRT_2_MSB REG(0x00, 0xb3) /* write */ |
||||
#define REG_VS_PIX_STRT_2_LSB REG(0x00, 0xb4) /* write */ |
||||
#define REG_VS_LINE_END_2_MSB REG(0x00, 0xb5) /* write */ |
||||
#define REG_VS_LINE_END_2_LSB REG(0x00, 0xb6) /* write */ |
||||
#define REG_VS_PIX_END_2_MSB REG(0x00, 0xb7) /* write */ |
||||
#define REG_VS_PIX_END_2_LSB REG(0x00, 0xb8) /* write */ |
||||
#define REG_HS_PIX_START_MSB REG(0x00, 0xb9) /* write */ |
||||
#define REG_HS_PIX_START_LSB REG(0x00, 0xba) /* write */ |
||||
#define REG_HS_PIX_STOP_MSB REG(0x00, 0xbb) /* write */ |
||||
#define REG_HS_PIX_STOP_LSB REG(0x00, 0xbc) /* write */ |
||||
#define REG_VWIN_START_1_MSB REG(0x00, 0xbd) /* write */ |
||||
#define REG_VWIN_START_1_LSB REG(0x00, 0xbe) /* write */ |
||||
#define REG_VWIN_END_1_MSB REG(0x00, 0xbf) /* write */ |
||||
#define REG_VWIN_END_1_LSB REG(0x00, 0xc0) /* write */ |
||||
#define REG_VWIN_START_2_MSB REG(0x00, 0xc1) /* write */ |
||||
#define REG_VWIN_START_2_LSB REG(0x00, 0xc2) /* write */ |
||||
#define REG_VWIN_END_2_MSB REG(0x00, 0xc3) /* write */ |
||||
#define REG_VWIN_END_2_LSB REG(0x00, 0xc4) /* write */ |
||||
#define REG_DE_START_MSB REG(0x00, 0xc5) /* write */ |
||||
#define REG_DE_START_LSB REG(0x00, 0xc6) /* write */ |
||||
#define REG_DE_STOP_MSB REG(0x00, 0xc7) /* write */ |
||||
#define REG_DE_STOP_LSB REG(0x00, 0xc8) /* write */ |
||||
#define REG_TBG_CNTRL_0 REG(0x00, 0xca) /* write */ |
||||
#define TBG_CNTRL_0_TOP_TGL BIT(0) |
||||
#define TBG_CNTRL_0_TOP_SEL BIT(1) |
||||
#define TBG_CNTRL_0_DE_EXT BIT(2) |
||||
#define TBG_CNTRL_0_TOP_EXT BIT(3) |
||||
#define TBG_CNTRL_0_FRAME_DIS BIT(5) |
||||
#define TBG_CNTRL_0_SYNC_MTHD BIT(6) |
||||
#define TBG_CNTRL_0_SYNC_ONCE BIT(7) |
||||
#define REG_TBG_CNTRL_1 REG(0x00, 0xcb) /* write */ |
||||
#define TBG_CNTRL_1_H_TGL BIT(0) |
||||
#define TBG_CNTRL_1_V_TGL BIT(1) |
||||
#define TBG_CNTRL_1_TGL_EN BIT(2) |
||||
#define TBG_CNTRL_1_X_EXT BIT(3) |
||||
#define TBG_CNTRL_1_H_EXT BIT(4) |
||||
#define TBG_CNTRL_1_V_EXT BIT(5) |
||||
#define TBG_CNTRL_1_DWIN_DIS BIT(6) |
||||
#define REG_ENABLE_SPACE REG(0x00, 0xd6) /* write */ |
||||
#define REG_HVF_CNTRL_0 REG(0x00, 0xe4) /* write */ |
||||
#define HVF_CNTRL_0_SM BIT(7) |
||||
#define HVF_CNTRL_0_RWB BIT(6) |
||||
#define HVF_CNTRL_0_PREFIL(x) (((x) & 3) << 2) |
||||
#define HVF_CNTRL_0_INTPOL(x) (((x) & 3) << 0) |
||||
#define REG_HVF_CNTRL_1 REG(0x00, 0xe5) /* write */ |
||||
#define HVF_CNTRL_1_FOR BIT(0) |
||||
#define HVF_CNTRL_1_YUVBLK BIT(1) |
||||
#define HVF_CNTRL_1_VQR(x) (((x) & 3) << 2) |
||||
#define HVF_CNTRL_1_PAD(x) (((x) & 3) << 4) |
||||
#define REG_RPT_CNTRL REG(0x00, 0xf0) /* write */ |
||||
#define REG_AIP_CLKSEL REG(0x00, 0xfd) /* write */ |
||||
#define AIP_CLKSEL_AIP_SPDIF (0 << 3) |
||||
#define AIP_CLKSEL_AIP_I2S BIT(3) |
||||
#define AIP_CLKSEL_FS_ACLK (0 << 0) |
||||
#define AIP_CLKSEL_FS_MCLK BIT(0) |
||||
|
||||
/* Page 02h: PLL settings */ |
||||
#define REG_PLL_SERIAL_1 REG(0x02, 0x00) /* read/write */ |
||||
#define PLL_SERIAL_1_SRL_FDN BIT(0) |
||||
#define PLL_SERIAL_1_SRL_IZ(x) (((x) & 3) << 1) |
||||
#define PLL_SERIAL_1_SRL_MAN_IZ BIT(6) |
||||
#define REG_PLL_SERIAL_2 REG(0x02, 0x01) /* read/write */ |
||||
#define PLL_SERIAL_2_SRL_NOSC(x) ((x) << 0) |
||||
#define PLL_SERIAL_2_SRL_PR(x) (((x) & 0xf) << 4) |
||||
#define REG_PLL_SERIAL_3 REG(0x02, 0x02) /* read/write */ |
||||
#define PLL_SERIAL_3_SRL_CCIR BIT(0) |
||||
#define PLL_SERIAL_3_SRL_DE BIT(2) |
||||
#define PLL_SERIAL_3_SRL_PXIN_SEL BIT(4) |
||||
#define REG_SERIALIZER REG(0x02, 0x03) /* read/write */ |
||||
#define REG_BUFFER_OUT REG(0x02, 0x04) /* read/write */ |
||||
#define REG_PLL_SCG1 REG(0x02, 0x05) /* read/write */ |
||||
#define REG_PLL_SCG2 REG(0x02, 0x06) /* read/write */ |
||||
#define REG_PLL_SCGN1 REG(0x02, 0x07) /* read/write */ |
||||
#define REG_PLL_SCGN2 REG(0x02, 0x08) /* read/write */ |
||||
#define REG_PLL_SCGR1 REG(0x02, 0x09) /* read/write */ |
||||
#define REG_PLL_SCGR2 REG(0x02, 0x0a) /* read/write */ |
||||
#define REG_AUDIO_DIV REG(0x02, 0x0e) /* read/write */ |
||||
#define AUDIO_DIV_SERCLK_1 0 |
||||
#define AUDIO_DIV_SERCLK_2 1 |
||||
#define AUDIO_DIV_SERCLK_4 2 |
||||
#define AUDIO_DIV_SERCLK_8 3 |
||||
#define AUDIO_DIV_SERCLK_16 4 |
||||
#define AUDIO_DIV_SERCLK_32 5 |
||||
#define REG_SEL_CLK REG(0x02, 0x11) /* read/write */ |
||||
#define SEL_CLK_SEL_CLK1 BIT(0) |
||||
#define SEL_CLK_SEL_VRF_CLK(x) (((x) & 3) << 1) |
||||
#define SEL_CLK_ENA_SC_CLK BIT(3) |
||||
#define REG_ANA_GENERAL REG(0x02, 0x12) /* read/write */ |
||||
|
||||
/* Page 09h: EDID Control */ |
||||
#define REG_EDID_DATA_0 REG(0x09, 0x00) /* read */ |
||||
/* next 127 successive registers are the EDID block */ |
||||
#define REG_EDID_CTRL REG(0x09, 0xfa) /* read/write */ |
||||
#define REG_DDC_ADDR REG(0x09, 0xfb) /* read/write */ |
||||
#define REG_DDC_OFFS REG(0x09, 0xfc) /* read/write */ |
||||
#define REG_DDC_SEGM_ADDR REG(0x09, 0xfd) /* read/write */ |
||||
#define REG_DDC_SEGM REG(0x09, 0xfe) /* read/write */ |
||||
|
||||
/* Page 11h: audio settings and content info packets */ |
||||
#define REG_AIP_CNTRL_0 REG(0x11, 0x00) /* read/write */ |
||||
#define AIP_CNTRL_0_RST_FIFO BIT(0) |
||||
#define REG_ENC_CNTRL REG(0x11, 0x0d) /* read/write */ |
||||
#define ENC_CNTRL_RST_ENC BIT(0) |
||||
#define ENC_CNTRL_RST_SEL BIT(1) |
||||
#define ENC_CNTRL_CTL_CODE(x) (((x) & 3) << 2) |
||||
|
||||
/* Page 12h: HDCP and OTP */ |
||||
#define REG_TX3 REG(0x12, 0x9a) /* read/write */ |
||||
#define REG_TX4 REG(0x12, 0x9b) /* read/write */ |
||||
#define TX4_PD_RAM BIT(1) |
||||
#define REG_TX33 REG(0x12, 0xb8) /* read/write */ |
||||
#define TX33_HDMI BIT(1) |
||||
|
||||
/* CEC registers, not paged */ |
||||
#define REG_CEC_FRO_IM_CLK_CTRL 0xfb /* read/write */ |
||||
#define CEC_FRO_IM_CLK_CTRL_GHOST_DIS BIT(7) |
||||
#define CEC_FRO_IM_CLK_CTRL_ENA_OTP BIT(6) |
||||
#define CEC_FRO_IM_CLK_CTRL_IMCLK_SEL BIT(1) |
||||
#define CEC_FRO_IM_CLK_CTRL_FRO_DIV BIT(0) |
||||
#define REG_CEC_RXSHPDINTENA 0xfc /* read/write */ |
||||
#define REG_CEC_RXSHPDINT 0xfd /* read */ |
||||
#define CEC_RXSHPDINT_RXSENS BIT(0) |
||||
#define CEC_RXSHPDINT_HPD BIT(1) |
||||
#define TDA19988_CEC_ENAMODS 0xff /* read/write */ |
||||
#define CEC_ENAMODS_EN_RXSENS BIT(2) |
||||
#define CEC_ENAMODS_EN_HDMI BIT(1) |
||||
#define CEC_ENAMODS_EN_CEC BIT(0) |
||||
|
||||
/* Device versions */ |
||||
#define TDA9989N2 0x0101 |
||||
#define TDA19989 0x0201 |
||||
#define TDA19989N2 0x0202 |
||||
#define TDA19988 0x0301 |
||||
|
||||
struct tda19988_priv { |
||||
struct udevice *chip; |
||||
struct udevice *cec_chip; |
||||
u16 revision; |
||||
u8 current_page; |
||||
}; |
||||
|
||||
static void tda19988_register_set(struct tda19988_priv *priv, u16 reg, u8 val) |
||||
{ |
||||
u8 old_val, page = REG2PAGE(reg); |
||||
|
||||
if (priv->current_page != page) { |
||||
dm_i2c_reg_write(priv->chip, REG_CURRENT_PAGE, page); |
||||
priv->current_page = page; |
||||
} |
||||
old_val = dm_i2c_reg_read(priv->chip, REG2ADDR(reg)); |
||||
old_val |= val; |
||||
dm_i2c_reg_write(priv->chip, REG2ADDR(reg), old_val); |
||||
} |
||||
|
||||
static void tda19988_register_clear(struct tda19988_priv *priv, u16 reg, u8 val) |
||||
{ |
||||
u8 old_val, page = REG2PAGE(reg); |
||||
|
||||
if (priv->current_page != page) { |
||||
dm_i2c_reg_write(priv->chip, REG_CURRENT_PAGE, page); |
||||
priv->current_page = page; |
||||
} |
||||
old_val = dm_i2c_reg_read(priv->chip, REG2ADDR(reg)); |
||||
old_val &= ~val; |
||||
dm_i2c_reg_write(priv->chip, REG2ADDR(reg), old_val); |
||||
} |
||||
|
||||
static void tda19988_register_write(struct tda19988_priv *priv, u16 reg, u8 val) |
||||
{ |
||||
u8 page = REG2PAGE(reg); |
||||
|
||||
if (priv->current_page != page) { |
||||
dm_i2c_reg_write(priv->chip, REG_CURRENT_PAGE, page); |
||||
priv->current_page = page; |
||||
} |
||||
dm_i2c_reg_write(priv->chip, REG2ADDR(reg), val); |
||||
} |
||||
|
||||
static int tda19988_register_read(struct tda19988_priv *priv, u16 reg) |
||||
{ |
||||
u8 page = REG2PAGE(reg); |
||||
|
||||
if (priv->current_page != page) { |
||||
dm_i2c_reg_write(priv->chip, REG_CURRENT_PAGE, page); |
||||
priv->current_page = page; |
||||
} |
||||
return dm_i2c_reg_read(priv->chip, REG2ADDR(reg)); |
||||
} |
||||
|
||||
static void tda19988_register_write16(struct tda19988_priv *priv, |
||||
u16 reg, u16 val) |
||||
{ |
||||
u8 buf[] = { val >> 8, val }, page = REG2PAGE(reg); |
||||
|
||||
if (priv->current_page != page) { |
||||
dm_i2c_reg_write(priv->chip, REG_CURRENT_PAGE, page); |
||||
priv->current_page = page; |
||||
} |
||||
dm_i2c_write(priv->chip, REG2ADDR(reg), buf, 2); |
||||
} |
||||
|
||||
static int tda19988_read_edid(struct udevice *dev, u8 *buf, int buf_size) |
||||
{ |
||||
struct tda19988_priv *priv = dev_get_priv(dev); |
||||
int i, val = 0, offset = 0; |
||||
|
||||
/*
|
||||
* The TDA998x has a problem when trying to read the EDID close to a |
||||
* HPD assertion: it needs a delay of 100ms to avoid timing out while |
||||
* trying to read EDID data. |
||||
*/ |
||||
mdelay(120); |
||||
|
||||
if (priv->revision == TDA19988) |
||||
tda19988_register_clear(priv, REG_TX4, TX4_PD_RAM); |
||||
|
||||
while (offset < buf_size) { |
||||
tda19988_register_write(priv, REG_DDC_ADDR, 0xa0); |
||||
tda19988_register_write(priv, REG_DDC_OFFS, offset); |
||||
tda19988_register_write(priv, REG_DDC_SEGM_ADDR, 0x60); |
||||
tda19988_register_write(priv, REG_DDC_SEGM, 0); |
||||
|
||||
/* enable reading EDID */ |
||||
tda19988_register_write(priv, REG_EDID_CTRL, 1); |
||||
|
||||
/* flags must be cleared by software */ |
||||
tda19988_register_write(priv, REG_EDID_CTRL, 0); |
||||
|
||||
/* wait for block read to complete */ |
||||
for (i = 300; i > 0; i--) { |
||||
mdelay(1); |
||||
val = tda19988_register_read(priv, REG_INT_FLAGS_2); |
||||
if (val < 0) |
||||
return val; |
||||
if (val & INT_FLAGS_2_EDID_BLK_RD) |
||||
break; |
||||
} |
||||
|
||||
if (i == 0) |
||||
return -ETIMEDOUT; |
||||
|
||||
priv->current_page = REG2PAGE(REG_EDID_DATA_0); |
||||
dm_i2c_reg_write(priv->chip, |
||||
REG_CURRENT_PAGE, REG2PAGE(REG_EDID_DATA_0)); |
||||
val = dm_i2c_read(priv->chip, |
||||
REG2ADDR(REG_EDID_DATA_0), buf + offset, 128); |
||||
offset += 128; |
||||
} |
||||
|
||||
if (priv->revision == TDA19988) |
||||
tda19988_register_set(priv, REG_TX4, TX4_PD_RAM); |
||||
|
||||
return offset; |
||||
} |
||||
|
||||
static int tda19988_enable(struct udevice *dev, int panel_bpp, |
||||
const struct display_timing *timing) |
||||
{ |
||||
struct tda19988_priv *priv = dev_get_priv(dev); |
||||
u8 div = 148500000 / timing->pixelclock.typ, reg; |
||||
u16 line_clocks, lines; |
||||
|
||||
if (dev != 0) { |
||||
div--; |
||||
if (div > 3) |
||||
div = 3; |
||||
} |
||||
/* first disable the video ports */ |
||||
tda19988_register_write(priv, REG_ENA_VP_0, 0); |
||||
tda19988_register_write(priv, REG_ENA_VP_1, 0); |
||||
tda19988_register_write(priv, REG_ENA_VP_2, 0); |
||||
|
||||
/* shutdown audio */ |
||||
tda19988_register_write(priv, REG_ENA_AP, 0); |
||||
|
||||
line_clocks = timing->hsync_len.typ + timing->hback_porch.typ + |
||||
timing->hactive.typ + timing->hfront_porch.typ; |
||||
lines = timing->vsync_len.typ + timing->vback_porch.typ + |
||||
timing->vactive.typ + timing->vfront_porch.typ; |
||||
|
||||
/* mute the audio FIFO */ |
||||
tda19988_register_set(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_FIFO); |
||||
/* HDMI HDCP: off */ |
||||
tda19988_register_write(priv, REG_TBG_CNTRL_1, TBG_CNTRL_1_DWIN_DIS); |
||||
tda19988_register_clear(priv, REG_TX33, TX33_HDMI); |
||||
tda19988_register_write(priv, REG_ENC_CNTRL, ENC_CNTRL_CTL_CODE(0)); |
||||
|
||||
/* no pre-filter or interpolator */ |
||||
tda19988_register_write(priv, REG_HVF_CNTRL_0, HVF_CNTRL_0_PREFIL(0) | |
||||
HVF_CNTRL_0_INTPOL(0)); |
||||
tda19988_register_set(priv, REG_FEAT_POWERDOWN, |
||||
FEAT_POWERDOWN_PREFILT); |
||||
tda19988_register_write(priv, REG_VIP_CNTRL_5, VIP_CNTRL_5_SP_CNT(0)); |
||||
tda19988_register_write(priv, REG_VIP_CNTRL_4, |
||||
VIP_CNTRL_4_BLANKIT(0) | VIP_CNTRL_4_BLC(0) | |
||||
VIP_CNTRL_4_TST_PAT); |
||||
|
||||
tda19988_register_clear(priv, REG_PLL_SERIAL_1, |
||||
PLL_SERIAL_1_SRL_MAN_IZ); |
||||
tda19988_register_clear(priv, REG_PLL_SERIAL_3, PLL_SERIAL_3_SRL_CCIR | |
||||
PLL_SERIAL_3_SRL_DE); |
||||
|
||||
tda19988_register_write(priv, REG_SERIALIZER, 0); |
||||
tda19988_register_write(priv, REG_HVF_CNTRL_1, HVF_CNTRL_1_VQR(0)); |
||||
|
||||
tda19988_register_write(priv, REG_RPT_CNTRL, 0); |
||||
tda19988_register_write(priv, REG_SEL_CLK, SEL_CLK_SEL_VRF_CLK(0) | |
||||
SEL_CLK_SEL_CLK1 | SEL_CLK_ENA_SC_CLK); |
||||
tda19988_register_write(priv, REG_PLL_SERIAL_2, |
||||
PLL_SERIAL_2_SRL_NOSC(div) | |
||||
PLL_SERIAL_2_SRL_PR(0)); |
||||
|
||||
/* set color matrix bypass flag: */ |
||||
tda19988_register_write(priv, REG_MAT_CONTRL, MAT_CONTRL_MAT_BP | |
||||
MAT_CONTRL_MAT_SC(1)); |
||||
tda19988_register_set(priv, REG_FEAT_POWERDOWN, FEAT_POWERDOWN_CSC); |
||||
|
||||
/* set BIAS tmds value: */ |
||||
tda19988_register_write(priv, REG_ANA_GENERAL, 0x09); |
||||
|
||||
/*
|
||||
* Sync on rising HSYNC/VSYNC |
||||
*/ |
||||
reg = VIP_CNTRL_3_SYNC_HS; |
||||
|
||||
/*
|
||||
* TDA19988 requires high-active sync at input stage, |
||||
* so invert low-active sync provided by master encoder here |
||||
*/ |
||||
if (timing->flags & DISPLAY_FLAGS_HSYNC_LOW) |
||||
reg |= VIP_CNTRL_3_H_TGL; |
||||
if (timing->flags & DISPLAY_FLAGS_VSYNC_LOW) |
||||
reg |= VIP_CNTRL_3_V_TGL; |
||||
tda19988_register_write(priv, REG_VIP_CNTRL_3, reg); |
||||
|
||||
tda19988_register_write(priv, REG_VIDFORMAT, 0x00); |
||||
tda19988_register_write16(priv, REG_REFPIX_MSB, |
||||
timing->hfront_porch.typ + 3); |
||||
tda19988_register_write16(priv, REG_REFLINE_MSB, |
||||
timing->vfront_porch.typ + 1); |
||||
tda19988_register_write16(priv, REG_NPIX_MSB, line_clocks); |
||||
tda19988_register_write16(priv, REG_NLINE_MSB, lines); |
||||
tda19988_register_write16(priv, REG_VS_LINE_STRT_1_MSB, |
||||
timing->vfront_porch.typ); |
||||
tda19988_register_write16(priv, REG_VS_PIX_STRT_1_MSB, |
||||
timing->hfront_porch.typ); |
||||
tda19988_register_write16(priv, REG_VS_LINE_END_1_MSB, |
||||
timing->vfront_porch.typ + |
||||
timing->vsync_len.typ); |
||||
tda19988_register_write16(priv, REG_VS_PIX_END_1_MSB, |
||||
timing->hfront_porch.typ); |
||||
tda19988_register_write16(priv, REG_VS_LINE_STRT_2_MSB, 0); |
||||
tda19988_register_write16(priv, REG_VS_PIX_STRT_2_MSB, 0); |
||||
tda19988_register_write16(priv, REG_VS_LINE_END_2_MSB, 0); |
||||
tda19988_register_write16(priv, REG_VS_PIX_END_2_MSB, 0); |
||||
tda19988_register_write16(priv, REG_HS_PIX_START_MSB, |
||||
timing->hfront_porch.typ); |
||||
tda19988_register_write16(priv, REG_HS_PIX_STOP_MSB, |
||||
timing->hfront_porch.typ + |
||||
timing->hsync_len.typ); |
||||
tda19988_register_write16(priv, REG_VWIN_START_1_MSB, |
||||
lines - timing->vactive.typ - 1); |
||||
tda19988_register_write16(priv, REG_VWIN_END_1_MSB, lines - 1); |
||||
tda19988_register_write16(priv, REG_VWIN_START_2_MSB, 0); |
||||
tda19988_register_write16(priv, REG_VWIN_END_2_MSB, 0); |
||||
tda19988_register_write16(priv, REG_DE_START_MSB, |
||||
line_clocks - timing->hactive.typ); |
||||
tda19988_register_write16(priv, REG_DE_STOP_MSB, line_clocks); |
||||
|
||||
if (priv->revision == TDA19988) { |
||||
/* let incoming pixels fill the active space (if any) */ |
||||
tda19988_register_write(priv, REG_ENABLE_SPACE, 0x00); |
||||
} |
||||
|
||||
/*
|
||||
* Always generate sync polarity relative to input sync and |
||||
* revert input stage toggled sync at output stage |
||||
*/ |
||||
reg = TBG_CNTRL_1_DWIN_DIS | TBG_CNTRL_1_TGL_EN; |
||||
if (timing->flags & DISPLAY_FLAGS_HSYNC_LOW) |
||||
reg |= TBG_CNTRL_1_H_TGL; |
||||
if (timing->flags & DISPLAY_FLAGS_VSYNC_LOW) |
||||
reg |= TBG_CNTRL_1_V_TGL; |
||||
tda19988_register_write(priv, REG_TBG_CNTRL_1, reg); |
||||
|
||||
/* must be last register set: */ |
||||
tda19988_register_write(priv, REG_TBG_CNTRL_0, 0); |
||||
|
||||
/* turn on HDMI HDCP */ |
||||
reg &= ~TBG_CNTRL_1_DWIN_DIS; |
||||
tda19988_register_write(priv, REG_TBG_CNTRL_1, reg); |
||||
tda19988_register_write(priv, REG_ENC_CNTRL, ENC_CNTRL_CTL_CODE(1)); |
||||
tda19988_register_set(priv, REG_TX33, TX33_HDMI); |
||||
|
||||
mdelay(400); |
||||
|
||||
/* enable video ports */ |
||||
tda19988_register_write(priv, REG_ENA_VP_0, 0xff); |
||||
tda19988_register_write(priv, REG_ENA_VP_1, 0xff); |
||||
tda19988_register_write(priv, REG_ENA_VP_2, 0xff); |
||||
/* set muxing after enabling ports: */ |
||||
tda19988_register_write(priv, REG_VIP_CNTRL_0, |
||||
VIP_CNTRL_0_SWAP_A(2) | VIP_CNTRL_0_SWAP_B(3)); |
||||
tda19988_register_write(priv, REG_VIP_CNTRL_1, |
||||
VIP_CNTRL_1_SWAP_C(4) | VIP_CNTRL_1_SWAP_D(5)); |
||||
tda19988_register_write(priv, REG_VIP_CNTRL_2, |
||||
VIP_CNTRL_2_SWAP_E(0) | VIP_CNTRL_2_SWAP_F(1)); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
struct dm_display_ops tda19988_ops = { |
||||
.read_edid = tda19988_read_edid, |
||||
.enable = tda19988_enable, |
||||
}; |
||||
|
||||
static const struct udevice_id tda19988_ids[] = { |
||||
{ .compatible = "nxp,tda998x" }, |
||||
{ } |
||||
}; |
||||
|
||||
static int tda19988_probe(struct udevice *dev) |
||||
{ |
||||
u8 cec_addr, chip_addr, rev_lo, rev_hi; |
||||
int err; |
||||
struct tda19988_priv *priv = dev_get_priv(dev); |
||||
|
||||
chip_addr = dev_read_addr(dev); |
||||
/* CEC I2C address is using TDA19988 I2C address configuration pins */ |
||||
cec_addr = 0x34 + (chip_addr & 0x03); |
||||
|
||||
err = i2c_get_chip_for_busnum(0, cec_addr, 1, &priv->cec_chip); |
||||
if (err) { |
||||
printf("cec i2c_get_chip_for_busnum returned %d\n", err); |
||||
return err; |
||||
} |
||||
|
||||
err = i2c_get_chip_for_busnum(0, chip_addr, 1, &priv->chip); |
||||
if (err) { |
||||
printf("i2c_get_chip_for_busnum returned %d\n", err); |
||||
return err; |
||||
} |
||||
|
||||
priv->current_page = 0xff; |
||||
|
||||
/* wake up device */ |
||||
dm_i2c_reg_write(priv->cec_chip, TDA19988_CEC_ENAMODS, |
||||
CEC_ENAMODS_EN_RXSENS | CEC_ENAMODS_EN_HDMI); |
||||
|
||||
/* reset audio and I2C master */ |
||||
tda19988_register_write(priv, REG_SOFTRESET, |
||||
SOFTRESET_AUDIO | SOFTRESET_I2C_MASTER); |
||||
mdelay(50); |
||||
tda19988_register_write(priv, REG_SOFTRESET, 0); |
||||
mdelay(50); |
||||
|
||||
/* reset transmitter */ |
||||
tda19988_register_set(priv, REG_MAIN_CNTRL0, MAIN_CNTRL0_SR); |
||||
tda19988_register_clear(priv, REG_MAIN_CNTRL0, MAIN_CNTRL0_SR); |
||||
|
||||
/* PLL registers common configuration */ |
||||
tda19988_register_write(priv, REG_PLL_SERIAL_1, 0x00); |
||||
tda19988_register_write(priv, REG_PLL_SERIAL_2, |
||||
PLL_SERIAL_2_SRL_NOSC(1)); |
||||
tda19988_register_write(priv, REG_PLL_SERIAL_3, 0x00); |
||||
tda19988_register_write(priv, REG_SERIALIZER, 0x00); |
||||
tda19988_register_write(priv, REG_BUFFER_OUT, 0x00); |
||||
tda19988_register_write(priv, REG_PLL_SCG1, 0x00); |
||||
tda19988_register_write(priv, REG_AUDIO_DIV, AUDIO_DIV_SERCLK_8); |
||||
tda19988_register_write(priv, REG_SEL_CLK, |
||||
SEL_CLK_SEL_CLK1 | SEL_CLK_ENA_SC_CLK); |
||||
tda19988_register_write(priv, REG_PLL_SCGN1, 0xfa); |
||||
tda19988_register_write(priv, REG_PLL_SCGN2, 0x00); |
||||
tda19988_register_write(priv, REG_PLL_SCGR1, 0x5b); |
||||
tda19988_register_write(priv, REG_PLL_SCGR2, 0x00); |
||||
tda19988_register_write(priv, REG_PLL_SCG2, 0x10); |
||||
|
||||
/* Write the default value MUX register */ |
||||
tda19988_register_write(priv, REG_MUX_VP_VIP_OUT, 0x24); |
||||
|
||||
/* read version */ |
||||
rev_lo = dm_i2c_reg_read(priv->chip, REG_VERSION_LSB); |
||||
rev_hi = dm_i2c_reg_read(priv->chip, REG_VERSION_MSB); |
||||
|
||||
/* mask off feature bits */ |
||||
priv->revision = ((rev_hi << 8) | rev_lo) & ~0x30; |
||||
|
||||
printf("HDMI: "); |
||||
switch (priv->revision) { |
||||
case TDA9989N2: |
||||
printf("TDA9989 n2\n"); |
||||
break; |
||||
case TDA19989: |
||||
printf("TDA19989\n"); |
||||
break; |
||||
case TDA19989N2: |
||||
printf("TDA19989 n2\n"); |
||||
break; |
||||
case TDA19988: |
||||
printf("TDA19988\n"); |
||||
break; |
||||
default: |
||||
printf("unknown TDA device: 0x%04x\n", priv->revision); |
||||
return -ENXIO; |
||||
} |
||||
|
||||
/* after reset, enable DDC */ |
||||
tda19988_register_write(priv, REG_DDC_DISABLE, 0x00); |
||||
|
||||
/* set clock on DDC channel */ |
||||
tda19988_register_write(priv, REG_TX3, 39); |
||||
|
||||
/* if necessary, disable multi-master */ |
||||
if (priv->revision == TDA19989) |
||||
tda19988_register_set(priv, REG_I2C_MASTER, I2C_MASTER_DIS_MM); |
||||
|
||||
dm_i2c_reg_write(priv->cec_chip, REG_CEC_FRO_IM_CLK_CTRL, |
||||
CEC_FRO_IM_CLK_CTRL_GHOST_DIS | |
||||
CEC_FRO_IM_CLK_CTRL_IMCLK_SEL); |
||||
/* ensure interrupts are disabled */ |
||||
dm_i2c_reg_write(priv->cec_chip, REG_CEC_RXSHPDINTENA, 0); |
||||
/* clear pending interrupts */ |
||||
dm_i2c_reg_read(priv->cec_chip, REG_CEC_RXSHPDINT); |
||||
tda19988_register_read(priv, REG_INT_FLAGS_0); |
||||
tda19988_register_read(priv, REG_INT_FLAGS_1); |
||||
tda19988_register_read(priv, REG_INT_FLAGS_2); |
||||
|
||||
/* enable EDID read irq */ |
||||
tda19988_register_set(priv, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
U_BOOT_DRIVER(tda19988) = { |
||||
.name = "tda19988", |
||||
.id = UCLASS_DISPLAY, |
||||
.of_match = tda19988_ids, |
||||
.ops = &tda19988_ops, |
||||
.probe = tda19988_probe, |
||||
.priv_auto_alloc_size = sizeof(struct tda19988_priv), |
||||
}; |
@ -0,0 +1,45 @@ |
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* (C) Copyright 2017 |
||||
* Mario Six, Guntermann & Drunck GmbH, mario.six@gdsys.cc |
||||
*/ |
||||
|
||||
#include <common.h> |
||||
#include <dm.h> |
||||
#include <video_osd.h> |
||||
|
||||
int video_osd_get_info(struct udevice *dev, struct video_osd_info *info) |
||||
{ |
||||
struct video_osd_ops *ops = video_osd_get_ops(dev); |
||||
|
||||
return ops->get_info(dev, info); |
||||
} |
||||
|
||||
int video_osd_set_mem(struct udevice *dev, uint col, uint row, u8 *buf, |
||||
size_t buflen, uint count) |
||||
{ |
||||
struct video_osd_ops *ops = video_osd_get_ops(dev); |
||||
|
||||
return ops->set_mem(dev, col, row, buf, buflen, count); |
||||
} |
||||
|
||||
int video_osd_set_size(struct udevice *dev, uint col, uint row) |
||||
{ |
||||
struct video_osd_ops *ops = video_osd_get_ops(dev); |
||||
|
||||
return ops->set_size(dev, col, row); |
||||
} |
||||
|
||||
int video_osd_print(struct udevice *dev, uint col, uint row, ulong color, |
||||
char *text) |
||||
{ |
||||
struct video_osd_ops *ops = video_osd_get_ops(dev); |
||||
|
||||
return ops->print(dev, col, row, color, text); |
||||
} |
||||
|
||||
UCLASS_DRIVER(video_osd) = { |
||||
.id = UCLASS_VIDEO_OSD, |
||||
.name = "video_osd", |
||||
.flags = DM_UC_FLAG_SEQ_ALIAS, |
||||
}; |
@ -0,0 +1,192 @@ |
||||
/* SPDX-License-Identifier: GPL-2.0+ */ |
||||
/*
|
||||
* (C) Copyright 2017 |
||||
* Mario Six, Guntermann & Drunck GmbH, mario.six@gdsys.cc |
||||
*/ |
||||
|
||||
#ifndef _VIDEO_OSD_H_ |
||||
#define _VIDEO_OSD_H_ |
||||
|
||||
struct video_osd_info { |
||||
/* The width of the OSD display in columns */ |
||||
uint width; |
||||
/* The height of the OSD display in rows */ |
||||
uint height; |
||||
/* The major version of the OSD device */ |
||||
uint major_version; |
||||
/* The minor version of the OSD device */ |
||||
uint minor_version; |
||||
}; |
||||
|
||||
/**
|
||||
* struct video_osd_ops - driver operations for OSD uclass |
||||
* |
||||
* The OSD uclass implements support for text-oriented on-screen displays, |
||||
* which are taken to be devices that independently display a graphical |
||||
* text-based overlay over the video output of an associated display. |
||||
* |
||||
* The functions defined by the uclass support writing text to the display in |
||||
* either a generic form (by specifying a string, a driver-specific color value |
||||
* for the text, and screen coordinates in rows and columns) or a |
||||
* driver-specific form (by specifying "raw" driver-specific data to display at |
||||
* a given coordinate). |
||||
* |
||||
* Functions to read device information and set the size of the virtual OSD |
||||
* screen (in rows and columns) are also supported. |
||||
* |
||||
* Drivers should support these operations unless otherwise noted. These |
||||
* operations are intended to be used by uclass code, not directly from |
||||
* other code. |
||||
*/ |
||||
struct video_osd_ops { |
||||
/**
|
||||
* get_info() - Get information about a OSD instance |
||||
* |
||||
* A OSD instance may keep some internal data about itself. This |
||||
* function can be used to access this data. |
||||
* |
||||
* @dev: OSD instance to query. |
||||
* @info: Pointer to a structure that takes the information read |
||||
* from the OSD instance. |
||||
* @return 0 if OK, -ve on error. |
||||
*/ |
||||
int (*get_info)(struct udevice *dev, struct video_osd_info *info); |
||||
|
||||
/**
|
||||
* set_mem() - Write driver-specific text data to OSD screen |
||||
* |
||||
* The passed data are device-specific, and it's up to the driver how |
||||
* to interpret them. How the count parameter is interpreted is also |
||||
* driver-specific; most likely the given data will be written to the |
||||
* OSD count times back-to-back, which is e.g. convenient for filling |
||||
* areas of the OSD with a single character. |
||||
* |
||||
* For example a invocation of |
||||
* |
||||
* video_osd_set_mem(dev, 0, 0, "A", 1, 10); |
||||
* |
||||
* will write the device-specific text data "A" to the positions (0, 0) |
||||
* to (9, 0) on the OSD. |
||||
* |
||||
* Device-specific text data may, e.g. be a special encoding of glyphs |
||||
* to display and color values in binary format. |
||||
* |
||||
* @dev: OSD instance to write to. |
||||
* @col: Horizontal character coordinate to write to. |
||||
* @row Vertical character coordinate to write to. |
||||
* @buf: Array containing device-specific data to write to the |
||||
* specified coordinate on the OSD screen. |
||||
* @buflen: Length of the data in the passed buffer (in byte). |
||||
* @count: Write count many repetitions of the given text data |
||||
* @return 0 if OK, -ve on error. |
||||
*/ |
||||
int (*set_mem)(struct udevice *dev, uint col, uint row, u8 *buf, |
||||
size_t buflen, uint count); |
||||
|
||||
/**
|
||||
* set_size() - Set the position and dimension of the OSD's |
||||
* writeable window |
||||
* |
||||
* @dev: OSD instance to write to. |
||||
* @col The number of characters in the window's columns |
||||
* @row The number of characters in the window's rows |
||||
* @return 0 if OK, -ve on error. |
||||
*/ |
||||
int (*set_size)(struct udevice *dev, uint col, uint row); |
||||
|
||||
/**
|
||||
* print() - Print a string in a given color to specified coordinates |
||||
* on the OSD |
||||
* |
||||
* @dev: OSD instance to write to. |
||||
* @col The x-coordinate of the position the string should be |
||||
* written to |
||||
* @row The y-coordinate of the position the string should be |
||||
* written to |
||||
* @color: The color in which the specified string should be |
||||
* printed; the interpretation of the value is |
||||
* driver-specific, and possible values should be defined |
||||
* e.g. in a driver include file. |
||||
* @text: The string data that should be printed on the OSD |
||||
* @return 0 if OK, -ve on error. |
||||
*/ |
||||
int (*print)(struct udevice *dev, uint col, uint row, ulong color, |
||||
char *text); |
||||
}; |
||||
|
||||
#define video_osd_get_ops(dev) ((struct video_osd_ops *)(dev)->driver->ops) |
||||
|
||||
/**
|
||||
* video_osd_get_info() - Get information about a OSD instance |
||||
* |
||||
* A OSD instance may keep some internal data about itself. This function can |
||||
* be used to access this data. |
||||
* |
||||
* @dev: OSD instance to query. |
||||
* @info: Pointer to a structure that takes the information read from the |
||||
* OSD instance. |
||||
* @return 0 if OK, -ve on error. |
||||
*/ |
||||
int video_osd_get_info(struct udevice *dev, struct video_osd_info *info); |
||||
|
||||
/**
|
||||
* video_osd_set_mem() - Write text data to OSD memory |
||||
* |
||||
* The passed data are device-specific, and it's up to the driver how to |
||||
* interpret them. How the count parameter is interpreted is also |
||||
* driver-specific; most likely the given data will be written to the OSD count |
||||
* times back-to-back, which is e.g. convenient for filling areas of the OSD |
||||
* with a single character. |
||||
* |
||||
* For example a invocation of |
||||
* |
||||
* video_osd_set_mem(dev, 0, 0, "A", 1, 10); |
||||
* |
||||
* will write the device-specific text data "A" to the positions (0, 0) to (9, |
||||
* 0) on the OSD. |
||||
* |
||||
* Device-specific text data may, e.g. be a special encoding of glyphs to |
||||
* display and color values in binary format. |
||||
* |
||||
* @dev: OSD instance to write to. |
||||
* @col: Horizontal character coordinate to write to. |
||||
* @row Vertical character coordinate to write to. |
||||
* @buf: Array containing device-specific data to write to the specified |
||||
* coordinate on the OSD screen. |
||||
* @buflen: Length of the data in the passed buffer (in byte). |
||||
* @count: Write count many repetitions of the given text data |
||||
* @return 0 if OK, -ve on error. |
||||
*/ |
||||
int video_osd_set_mem(struct udevice *dev, uint col, uint row, u8 *buf, |
||||
size_t buflen, uint count); |
||||
|
||||
/**
|
||||
* video_osd_set_size() - Set the position and dimension of the OSD's |
||||
* writeable window |
||||
* |
||||
* @dev: OSD instance to write to. |
||||
* @col The number of characters in the window's columns |
||||
* @row The number of characters in the window's rows |
||||
* @return 0 if OK, -ve on error. |
||||
*/ |
||||
int video_osd_set_size(struct udevice *dev, uint col, uint row); |
||||
|
||||
/**
|
||||
* video_osd_print() - Print a string in a given color to specified coordinates |
||||
* on the OSD |
||||
* |
||||
* @dev: OSD instance to write to. |
||||
* @col The x-coordinate of the position the string should be written |
||||
* to |
||||
* @row The y-coordinate of the position the string should be written |
||||
* to |
||||
* @color: The color in which the specified string should be printed; the |
||||
* interpretation of the value is driver-specific, and possible |
||||
* values should be defined e.g. in a driver include file. |
||||
* @text: The string data that should be printed on the OSD |
||||
* @return 0 if OK, -ve on error. |
||||
*/ |
||||
int video_osd_print(struct udevice *dev, uint col, uint row, ulong color, |
||||
char *text); |
||||
|
||||
#endif /* !_VIDEO_OSD_H_ */ |
@ -0,0 +1,210 @@ |
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* (C) Copyright 2018 |
||||
* Mario Six, Guntermann & Drunck GmbH, mario.six@gdsys.cc |
||||
*/ |
||||
|
||||
#include <common.h> |
||||
#include <display_options.h> |
||||
#include <dm.h> |
||||
#include <dm/test.h> |
||||
#include <test/ut.h> |
||||
#include <video_osd.h> |
||||
#include <asm/test.h> |
||||
|
||||
#include "../../drivers/video/sandbox_osd.h" |
||||
|
||||
const uint memsize = 2 * 10 * 10; |
||||
|
||||
static void split(u8 *mem, uint size, u8 *text, u8 *colors) |
||||
{ |
||||
int i; |
||||
u16 *p = (u16 *)mem; |
||||
|
||||
for (i = 0; i < size; i++) { |
||||
colors[i] = p[i] % 0x100; |
||||
text[i] = p[i] / 0x100; |
||||
} |
||||
} |
||||
|
||||
static void print_mem(u8 *mem, uint width, uint height) |
||||
{ |
||||
const uint memsize = 2 * 10 * 10; |
||||
u8 colors[memsize / 2]; |
||||
u8 text[memsize / 2]; |
||||
int i; |
||||
|
||||
split(mem, memsize / 2, text, colors); |
||||
|
||||
for (i = 0; i < width * height; i++) { |
||||
printf("%c", text[i]); |
||||
if (i > 0 && ((i + 1) % width) == 0) |
||||
printf("\n"); |
||||
} |
||||
|
||||
printf("\n"); |
||||
|
||||
for (i = 0; i < width * height; i++) { |
||||
printf("%c", colors[i]); |
||||
if (i > 0 && ((i + 1) % width) == 0) |
||||
printf("\n"); |
||||
} |
||||
} |
||||
|
||||
static int dm_test_osd_basics(struct unit_test_state *uts) |
||||
{ |
||||
struct udevice *dev; |
||||
u8 mem[memsize + 1]; |
||||
u8 colors[memsize / 2]; |
||||
u8 text[memsize / 2]; |
||||
struct video_osd_info info; |
||||
|
||||
ut_assertok(uclass_first_device_err(UCLASS_VIDEO_OSD, &dev)); |
||||
|
||||
video_osd_get_info(dev, &info); |
||||
|
||||
ut_asserteq(10, info.width); |
||||
ut_asserteq(10, info.height); |
||||
ut_asserteq(1, info.major_version); |
||||
ut_asserteq(0, info.minor_version); |
||||
|
||||
ut_assertok(sandbox_osd_get_mem(dev, mem, memsize)); |
||||
split(mem, memsize / 2, text, colors); |
||||
|
||||
ut_assertok(memcmp(text, " " |
||||
" " |
||||
" " |
||||
" " |
||||
" " |
||||
" " |
||||
" " |
||||
" " |
||||
" " |
||||
" ", memsize / 2)); |
||||
|
||||
ut_assertok(memcmp(colors, "kkkkkkkkkk" |
||||
"kkkkkkkkkk" |
||||
"kkkkkkkkkk" |
||||
"kkkkkkkkkk" |
||||
"kkkkkkkkkk" |
||||
"kkkkkkkkkk" |
||||
"kkkkkkkkkk" |
||||
"kkkkkkkkkk" |
||||
"kkkkkkkkkk" |
||||
"kkkkkkkkkk", memsize / 2)); |
||||
|
||||
print_mem(mem, 10, 10); |
||||
|
||||
ut_assertok(video_osd_print(dev, 1, 1, COLOR_RED, "Blah")); |
||||
|
||||
ut_assertok(sandbox_osd_get_mem(dev, mem, memsize)); |
||||
split(mem, memsize / 2, text, colors); |
||||
|
||||
ut_assertok(memcmp(text, " " |
||||
" Blah " |
||||
" " |
||||
" " |
||||
" " |
||||
" " |
||||
" " |
||||
" " |
||||
" " |
||||
" ", memsize / 2)); |
||||
|
||||
ut_assertok(memcmp(colors, "kkkkkkkkkk" |
||||
"krrrrkkkkk" |
||||
"kkkkkkkkkk" |
||||
"kkkkkkkkkk" |
||||
"kkkkkkkkkk" |
||||
"kkkkkkkkkk" |
||||
"kkkkkkkkkk" |
||||
"kkkkkkkkkk" |
||||
"kkkkkkkkkk" |
||||
"kkkkkkkkkk", memsize / 2)); |
||||
|
||||
print_mem(mem, 10, 10); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
DM_TEST(dm_test_osd_basics, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); |
||||
|
||||
static int dm_test_osd_extended(struct unit_test_state *uts) |
||||
{ |
||||
struct udevice *dev; |
||||
u8 mem[memsize + 1]; |
||||
u8 colors[memsize / 2]; |
||||
u8 text[memsize / 2]; |
||||
struct video_osd_info info; |
||||
u16 val; |
||||
|
||||
ut_assertok(uclass_first_device_err(UCLASS_VIDEO_OSD, &dev)); |
||||
|
||||
ut_assertok(video_osd_set_size(dev, 20, 5)); |
||||
|
||||
video_osd_get_info(dev, &info); |
||||
|
||||
ut_asserteq(20, info.width); |
||||
ut_asserteq(5, info.height); |
||||
ut_asserteq(1, info.major_version); |
||||
ut_asserteq(0, info.minor_version); |
||||
|
||||
ut_assertok(sandbox_osd_get_mem(dev, mem, memsize)); |
||||
split(mem, memsize / 2, text, colors); |
||||
|
||||
ut_assertok(memcmp(text, " " |
||||
" " |
||||
" " |
||||
" " |
||||
" ", memsize / 2)); |
||||
|
||||
ut_assertok(memcmp(colors, "kkkkkkkkkkkkkkkkkkkk" |
||||
"kkkkkkkkkkkkkkkkkkkk" |
||||
"kkkkkkkkkkkkkkkkkkkk" |
||||
"kkkkkkkkkkkkkkkkkkkk" |
||||
"kkkkkkkkkkkkkkkkkkkk", memsize / 2)); |
||||
|
||||
print_mem(mem, 20, 5); |
||||
|
||||
/* Draw green border */ |
||||
val = '-' * 0x100 + 'g'; |
||||
ut_assertok(video_osd_set_mem(dev, 1, 0, (u8 *)&val, 2, 18)); |
||||
ut_assertok(video_osd_set_mem(dev, 1, 4, (u8 *)&val, 2, 18)); |
||||
ut_assertok(video_osd_print(dev, 0, 1, COLOR_GREEN, "|")); |
||||
ut_assertok(video_osd_print(dev, 0, 2, COLOR_GREEN, "|")); |
||||
ut_assertok(video_osd_print(dev, 0, 3, COLOR_GREEN, "|")); |
||||
ut_assertok(video_osd_print(dev, 19, 1, COLOR_GREEN, "|")); |
||||
ut_assertok(video_osd_print(dev, 19, 2, COLOR_GREEN, "|")); |
||||
ut_assertok(video_osd_print(dev, 19, 3, COLOR_GREEN, "|")); |
||||
ut_assertok(video_osd_print(dev, 0, 0, COLOR_GREEN, "+")); |
||||
ut_assertok(video_osd_print(dev, 19, 0, COLOR_GREEN, "+")); |
||||
ut_assertok(video_osd_print(dev, 19, 4, COLOR_GREEN, "+")); |
||||
ut_assertok(video_osd_print(dev, 0, 4, COLOR_GREEN, "+")); |
||||
|
||||
/* Add menu caption and entries */ |
||||
ut_assertok(video_osd_print(dev, 5, 0, COLOR_GREEN, " OSD menu ")); |
||||
ut_assertok(video_osd_print(dev, 2, 1, COLOR_BLUE, " * Entry 1")); |
||||
ut_assertok(video_osd_print(dev, 2, 2, COLOR_BLUE, "(*) Entry 2")); |
||||
ut_assertok(video_osd_print(dev, 2, 3, COLOR_BLUE, " * Entry 3")); |
||||
|
||||
ut_assertok(sandbox_osd_get_mem(dev, mem, memsize)); |
||||
split(mem, memsize / 2, text, colors); |
||||
|
||||
print_mem(mem, 20, 5); |
||||
|
||||
ut_assertok(memcmp(text, "+---- OSD menu ----+" |
||||
"| * Entry 1 |" |
||||
"| (*) Entry 2 |" |
||||
"| * Entry 3 |" |
||||
"+------------------+", memsize / 2)); |
||||
|
||||
ut_assertok(memcmp(colors, "gggggggggggggggggggg" |
||||
"gkbbbbbbbbbbbkkkkkkg" |
||||
"gkbbbbbbbbbbbkkkkkkg" |
||||
"gkbbbbbbbbbbbkkkkkkg" |
||||
"gggggggggggggggggggg", memsize / 2)); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
DM_TEST(dm_test_osd_extended, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); |
@ -0,0 +1,5 @@ |
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
#
|
||||
# (C) Copyright 2018
|
||||
# Mario Six, Guntermann & Drunck GmbH, mario.six@gdsys.cc
|
||||
obj-y += hexdump.o
|
@ -0,0 +1,95 @@ |
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* (C) Copyright 2018 |
||||
* Mario Six, Guntermann & Drunck GmbH, mario.six@gdsys.cc |
||||
*/ |
||||
|
||||
#include <common.h> |
||||
#include <hexdump.h> |
||||
#include <dm/test.h> |
||||
#include <test/ut.h> |
||||
|
||||
static int lib_test_hex_to_bin(struct unit_test_state *uts) |
||||
{ |
||||
return 0; |
||||
|
||||
ut_asserteq(0x0, hex_to_bin('0')); |
||||
ut_asserteq(0x1, hex_to_bin('1')); |
||||
ut_asserteq(0x2, hex_to_bin('2')); |
||||
ut_asserteq(0x3, hex_to_bin('3')); |
||||
ut_asserteq(0x4, hex_to_bin('4')); |
||||
ut_asserteq(0x5, hex_to_bin('5')); |
||||
ut_asserteq(0x6, hex_to_bin('6')); |
||||
ut_asserteq(0x7, hex_to_bin('7')); |
||||
ut_asserteq(0x8, hex_to_bin('8')); |
||||
ut_asserteq(0x9, hex_to_bin('9')); |
||||
ut_asserteq(0xa, hex_to_bin('a')); |
||||
ut_asserteq(0xb, hex_to_bin('b')); |
||||
ut_asserteq(0xc, hex_to_bin('c')); |
||||
ut_asserteq(0xd, hex_to_bin('d')); |
||||
ut_asserteq(0xe, hex_to_bin('e')); |
||||
ut_asserteq(0xf, hex_to_bin('f')); |
||||
ut_asserteq(-1, hex_to_bin('g')); |
||||
} |
||||
|
||||
DM_TEST(lib_test_hex_to_bin, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); |
||||
|
||||
static int lib_test_hex2bin(struct unit_test_state *uts) |
||||
{ |
||||
u8 dst[4]; |
||||
|
||||
hex2bin(dst, "649421de", 4); |
||||
ut_asserteq_mem("\x64\x94\x21\xde", dst, 4); |
||||
hex2bin(dst, "aa2e7545", 4); |
||||
ut_asserteq_mem("\xaa\x2e\x75\x45", dst, 4); |
||||
hex2bin(dst, "75453bc5", 4); |
||||
ut_asserteq_mem("\x75\x45\x3b\xc5", dst, 4); |
||||
hex2bin(dst, "a16884c3", 4); |
||||
ut_asserteq_mem("\xa1\x68\x84\xc3", dst, 4); |
||||
hex2bin(dst, "156b2e5e", 4); |
||||
ut_asserteq_mem("\x15\x6b\x2e\x5e", dst, 4); |
||||
hex2bin(dst, "2e035fff", 4); |
||||
ut_asserteq_mem("\x2e\x03\x5f\xff", dst, 4); |
||||
hex2bin(dst, "0ffce99f", 4); |
||||
ut_asserteq_mem("\x0f\xfc\xe9\x9f", dst, 4); |
||||
hex2bin(dst, "d3999443", 4); |
||||
ut_asserteq_mem("\xd3\x99\x94\x43", dst, 4); |
||||
hex2bin(dst, "91dd87bc", 4); |
||||
ut_asserteq_mem("\x91\xdd\x87\xbc", dst, 4); |
||||
hex2bin(dst, "7fec8963", 4); |
||||
ut_asserteq_mem("\x7f\xec\x89\x63", dst, 4); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
DM_TEST(lib_test_hex2bin, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); |
||||
|
||||
static int lib_test_bin2hex(struct unit_test_state *uts) |
||||
{ |
||||
char dst[8 + 1] = "\0"; |
||||
|
||||
bin2hex(dst, "\x64\x94\x21\xde", 4); |
||||
ut_asserteq_str("649421de", dst); |
||||
bin2hex(dst, "\xaa\x2e\x75\x45", 4); |
||||
ut_asserteq_str("aa2e7545", dst); |
||||
bin2hex(dst, "\x75\x45\x3b\xc5", 4); |
||||
ut_asserteq_str("75453bc5", dst); |
||||
bin2hex(dst, "\xa1\x68\x84\xc3", 4); |
||||
ut_asserteq_str("a16884c3", dst); |
||||
bin2hex(dst, "\x15\x6b\x2e\x5e", 4); |
||||
ut_asserteq_str("156b2e5e", dst); |
||||
bin2hex(dst, "\x2e\x03\x5f\xff", 4); |
||||
ut_asserteq_str("2e035fff", dst); |
||||
bin2hex(dst, "\x0f\xfc\xe9\x9f", 4); |
||||
ut_asserteq_str("0ffce99f", dst); |
||||
bin2hex(dst, "\xd3\x99\x94\x43", 4); |
||||
ut_asserteq_str("d3999443", dst); |
||||
bin2hex(dst, "\x91\xdd\x87\xbc", 4); |
||||
ut_asserteq_str("91dd87bc", dst); |
||||
bin2hex(dst, "\x7f\xec\x89\x63", 4); |
||||
ut_asserteq_str("7fec8963", dst); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
DM_TEST(lib_test_bin2hex, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); |
Loading…
Reference in new issue