19671f696SMario Six // SPDX-License-Identifier: GPL-2.0+
29671f696SMario Six /*
39671f696SMario Six  * (C) Copyright 2017
49671f696SMario Six  * Mario Six, Guntermann & Drunck GmbH, mario.six@gdsys.cc
59671f696SMario Six  *
69671f696SMario Six  * based on the gdsys osd driver, which is
79671f696SMario Six  *
89671f696SMario Six  * (C) Copyright 2010
99671f696SMario Six  * Dirk Eibach, Guntermann & Drunck GmbH, dirk.eibach@gdsys.de
109671f696SMario Six  */
119671f696SMario Six 
129671f696SMario Six #include <common.h>
139671f696SMario Six #include <display.h>
149671f696SMario Six #include <dm.h>
159671f696SMario Six #include <regmap.h>
169671f696SMario Six #include <video_osd.h>
179671f696SMario Six #include <asm/gpio.h>
189671f696SMario Six 
199671f696SMario Six static const uint MAX_X_CHARS = 53;
209671f696SMario Six static const uint MAX_Y_CHARS = 26;
219671f696SMario Six static const uint MAX_VIDEOMEM_WIDTH = 64;
229671f696SMario Six static const uint MAX_VIDEOMEM_HEIGHT = 32;
239671f696SMario Six static const uint CHAR_WIDTH = 12;
249671f696SMario Six static const uint CHAR_HEIGHT = 18;
259671f696SMario Six 
269671f696SMario Six static const u16 BASE_WIDTH_MASK = 0x3f00;
279671f696SMario Six static const uint BASE_WIDTH_SHIFT = 8;
289671f696SMario Six static const u16 BASE_HEIGTH_MASK = 0x001f;
299671f696SMario Six static const uint BASE_HEIGTH_SHIFT;
309671f696SMario Six 
319671f696SMario Six struct ihs_video_out_regs {
329671f696SMario Six 	/* Device version register */
339671f696SMario Six 	u16 versions;
349671f696SMario Six 	/* Device feature register */
359671f696SMario Six 	u16 features;
369671f696SMario Six 	/* Device control register */
379671f696SMario Six 	u16 control;
389671f696SMario Six 	/* Register controlling screen size */
399671f696SMario Six 	u16 xy_size;
409671f696SMario Six 	/* Register controlling screen scaling */
419671f696SMario Six 	u16 xy_scale;
429671f696SMario Six 	/* Register controlling screen x position */
439671f696SMario Six 	u16 x_pos;
449671f696SMario Six 	/* Register controlling screen y position */
459671f696SMario Six 	u16 y_pos;
469671f696SMario Six };
479671f696SMario Six 
489671f696SMario Six #define ihs_video_out_set(map, member, val) \
499671f696SMario Six 	regmap_range_set(map, 1, struct ihs_video_out_regs, member, val)
509671f696SMario Six 
519671f696SMario Six #define ihs_video_out_get(map, member, valp) \
529671f696SMario Six 	regmap_range_get(map, 1, struct ihs_video_out_regs, member, valp)
539671f696SMario Six 
549671f696SMario Six enum {
559671f696SMario Six 	CONTROL_FILTER_BLACK = (0 << 0),
569671f696SMario Six 	CONTROL_FILTER_ORIGINAL = (1 << 0),
579671f696SMario Six 	CONTROL_FILTER_DARKER = (2 << 0),
589671f696SMario Six 	CONTROL_FILTER_GRAY = (3 << 0),
599671f696SMario Six 
609671f696SMario Six 	CONTROL_MODE_PASSTHROUGH = (0 << 3),
619671f696SMario Six 	CONTROL_MODE_OSD = (1 << 3),
629671f696SMario Six 	CONTROL_MODE_AUTO = (2 << 3),
639671f696SMario Six 	CONTROL_MODE_OFF = (3 << 3),
649671f696SMario Six 
659671f696SMario Six 	CONTROL_ENABLE_OFF = (0 << 6),
669671f696SMario Six 	CONTROL_ENABLE_ON = (1 << 6),
679671f696SMario Six };
689671f696SMario Six 
699671f696SMario Six struct ihs_video_out_priv {
709671f696SMario Six 	/* Register map for OSD device */
719671f696SMario Six 	struct regmap *map;
729671f696SMario Six 	/* Pointer to video memory */
739671f696SMario Six 	u16 *vidmem;
749671f696SMario Six 	/* Display width in text columns */
759671f696SMario Six 	uint base_width;
769671f696SMario Six 	/* Display height in text rows */
779671f696SMario Six 	uint base_height;
789671f696SMario Six 	/* x-resolution of the display in pixels */
799671f696SMario Six 	uint res_x;
809671f696SMario Six 	/* y-resolution of the display in pixels */
819671f696SMario Six 	uint res_y;
829671f696SMario Six 	/* OSD's sync mode (resolution + frequency) */
839671f696SMario Six 	int sync_src;
849671f696SMario Six 	/* The display port output for this OSD */
859671f696SMario Six 	struct udevice *video_tx;
869671f696SMario Six 	/* The pixel clock generator for the display */
879671f696SMario Six 	struct udevice *clk_gen;
889671f696SMario Six };
899671f696SMario Six 
909671f696SMario Six static const struct udevice_id ihs_video_out_ids[] = {
919671f696SMario Six 	{ .compatible = "gdsys,ihs_video_out" },
929671f696SMario Six 	{ }
939671f696SMario Six };
949671f696SMario Six 
959671f696SMario Six /**
969671f696SMario Six  * set_control() - Set the control register to a given value
979671f696SMario Six  *
989671f696SMario Six  * The current value of sync_src is preserved by the function automatically.
999671f696SMario Six  *
1009671f696SMario Six  * @dev: the OSD device whose control register to set
1019671f696SMario Six  * @value: the 16-bit value to write to the control register
1029671f696SMario Six  * Return: 0
1039671f696SMario Six  */
set_control(struct udevice * dev,u16 value)1049671f696SMario Six static int set_control(struct udevice *dev, u16 value)
1059671f696SMario Six {
1069671f696SMario Six 	struct ihs_video_out_priv *priv = dev_get_priv(dev);
1079671f696SMario Six 
1089671f696SMario Six 	if (priv->sync_src)
1099671f696SMario Six 		value |= ((priv->sync_src & 0x7) << 8);
1109671f696SMario Six 
1119671f696SMario Six 	ihs_video_out_set(priv->map, control, value);
1129671f696SMario Six 
1139671f696SMario Six 	return 0;
1149671f696SMario Six }
1159671f696SMario Six 
ihs_video_out_get_info(struct udevice * dev,struct video_osd_info * info)1169671f696SMario Six int ihs_video_out_get_info(struct udevice *dev, struct video_osd_info *info)
1179671f696SMario Six {
1189671f696SMario Six 	struct ihs_video_out_priv *priv = dev_get_priv(dev);
1199671f696SMario Six 	u16 versions;
1209671f696SMario Six 
1219671f696SMario Six 	ihs_video_out_get(priv->map, versions, &versions);
1229671f696SMario Six 
1239671f696SMario Six 	info->width = priv->base_width;
1249671f696SMario Six 	info->height = priv->base_height;
1259671f696SMario Six 	info->major_version = versions / 100;
1269671f696SMario Six 	info->minor_version = versions % 100;
1279671f696SMario Six 
1289671f696SMario Six 	return 0;
1299671f696SMario Six }
1309671f696SMario Six 
ihs_video_out_set_mem(struct udevice * dev,uint col,uint row,u8 * buf,size_t buflen,uint count)1319671f696SMario Six int ihs_video_out_set_mem(struct udevice *dev, uint col, uint row, u8 *buf,
1329671f696SMario Six 			  size_t buflen, uint count)
1339671f696SMario Six {
1349671f696SMario Six 	struct ihs_video_out_priv *priv = dev_get_priv(dev);
1359671f696SMario Six 	int res;
1369671f696SMario Six 	uint offset;
1379671f696SMario Six 	uint k, rep;
1389671f696SMario Six 	u16 data;
1399671f696SMario Six 
1409671f696SMario Six 	/* Repetitions (controlled via count parmeter) */
1419671f696SMario Six 	for (rep = 0; rep < count; ++rep) {
1429671f696SMario Six 		offset = row * priv->base_width + col + rep * (buflen / 2);
1439671f696SMario Six 
1449671f696SMario Six 		/* Write a single buffer copy */
1459671f696SMario Six 		for (k = 0; k < buflen / 2; ++k) {
1469671f696SMario Six 			uint max_size = priv->base_width * priv->base_height;
1479671f696SMario Six 
1489671f696SMario Six 			if (offset + k >= max_size) {
1499671f696SMario Six 				debug("%s: Write would be out of OSD bounds\n",
1509671f696SMario Six 				      dev->name);
1519671f696SMario Six 				return -E2BIG;
1529671f696SMario Six 			}
1539671f696SMario Six 
1549671f696SMario Six 			data = buf[2 * k + 1] + 256 * buf[2 * k];
1559671f696SMario Six 			out_le16(priv->vidmem + offset + k, data);
1569671f696SMario Six 		}
1579671f696SMario Six 	}
1589671f696SMario Six 
1599671f696SMario Six 	res = set_control(dev, CONTROL_FILTER_ORIGINAL |
1609671f696SMario Six 			       CONTROL_MODE_OSD |
1619671f696SMario Six 			       CONTROL_ENABLE_ON);
1629671f696SMario Six 	if (res) {
1639671f696SMario Six 		debug("%s: Could not set control register\n", dev->name);
1649671f696SMario Six 		return res;
1659671f696SMario Six 	}
1669671f696SMario Six 
1679671f696SMario Six 	return 0;
1689671f696SMario Six }
1699671f696SMario Six 
1709671f696SMario Six /**
1719671f696SMario Six  * div2_u16() - Approximately divide a 16-bit number by 2
1729671f696SMario Six  *
1739671f696SMario Six  * @val: The 16-bit value to divide by two
1749671f696SMario Six  * Return: The approximate division of val by two
1759671f696SMario Six  */
div2_u16(u16 val)1769671f696SMario Six static inline u16 div2_u16(u16 val)
1779671f696SMario Six {
1789671f696SMario Six 	return (32767 * val) / 65535;
1799671f696SMario Six }
1809671f696SMario Six 
ihs_video_out_set_size(struct udevice * dev,uint col,uint row)1819671f696SMario Six int ihs_video_out_set_size(struct udevice *dev, uint col, uint row)
1829671f696SMario Six {
1839671f696SMario Six 	struct ihs_video_out_priv *priv = dev_get_priv(dev);
1849671f696SMario Six 
1859671f696SMario Six 	if (!col || col > MAX_VIDEOMEM_WIDTH || col > MAX_X_CHARS ||
1869671f696SMario Six 	    !row || row > MAX_VIDEOMEM_HEIGHT || row > MAX_Y_CHARS) {
1879671f696SMario Six 		debug("%s: Desired OSD size invalid\n", dev->name);
1889671f696SMario Six 		return -EINVAL;
1899671f696SMario Six 	}
1909671f696SMario Six 
1919671f696SMario Six 	ihs_video_out_set(priv->map, xy_size, ((col - 1) << 8) | (row - 1));
1929671f696SMario Six 	/* Center OSD on screen */
1939671f696SMario Six 	ihs_video_out_set(priv->map, x_pos,
1949671f696SMario Six 			  div2_u16(priv->res_x - CHAR_WIDTH * col));
1959671f696SMario Six 	ihs_video_out_set(priv->map, y_pos,
1969671f696SMario Six 			  div2_u16(priv->res_y - CHAR_HEIGHT * row));
1979671f696SMario Six 
1989671f696SMario Six 	return 0;
1999671f696SMario Six }
2009671f696SMario Six 
ihs_video_out_print(struct udevice * dev,uint col,uint row,ulong color,char * text)2019671f696SMario Six int ihs_video_out_print(struct udevice *dev, uint col, uint row, ulong color,
2029671f696SMario Six 			char *text)
2039671f696SMario Six {
2049671f696SMario Six 	int res;
2059671f696SMario Six 	u8 buffer[2 * MAX_VIDEOMEM_WIDTH];
2069671f696SMario Six 	uint k;
2079671f696SMario Six 	uint charcount = strlen(text);
2089671f696SMario Six 	uint len = min(charcount, 2 * MAX_VIDEOMEM_WIDTH);
2099671f696SMario Six 
2109671f696SMario Six 	for (k = 0; k < len; ++k) {
2119671f696SMario Six 		buffer[2 * k] = text[k];
2129671f696SMario Six 		buffer[2 * k + 1] = color;
2139671f696SMario Six 	}
2149671f696SMario Six 
2159671f696SMario Six 	res = ihs_video_out_set_mem(dev, col, row, buffer, 2 * len, 1);
2169671f696SMario Six 	if (res < 0) {
2179671f696SMario Six 		debug("%s: Could not write to video memory\n", dev->name);
2189671f696SMario Six 		return res;
2199671f696SMario Six 	}
2209671f696SMario Six 
2219671f696SMario Six 	return 0;
2229671f696SMario Six }
2239671f696SMario Six 
2249671f696SMario Six static const struct video_osd_ops ihs_video_out_ops = {
2259671f696SMario Six 	.get_info = ihs_video_out_get_info,
2269671f696SMario Six 	.set_mem = ihs_video_out_set_mem,
2279671f696SMario Six 	.set_size = ihs_video_out_set_size,
2289671f696SMario Six 	.print = ihs_video_out_print,
2299671f696SMario Six };
2309671f696SMario Six 
ihs_video_out_probe(struct udevice * dev)2319671f696SMario Six int ihs_video_out_probe(struct udevice *dev)
2329671f696SMario Six {
2339671f696SMario Six 	struct ihs_video_out_priv *priv = dev_get_priv(dev);
2349671f696SMario Six 	struct ofnode_phandle_args phandle_args;
2359671f696SMario Six 	const char *mode;
2369671f696SMario Six 	u16 features;
2379671f696SMario Six 	struct display_timing timing;
2389671f696SMario Six 	int res;
2399671f696SMario Six 
2409671f696SMario Six 	res = regmap_init_mem(dev_ofnode(dev), &priv->map);
241*6df07d85SMario Six 	if (res) {
242*6df07d85SMario Six 		debug("%s: Could not initialize regmap (err = %d)\n", dev->name,
2439671f696SMario Six 		      res);
2449671f696SMario Six 		return res;
2459671f696SMario Six 	}
2469671f696SMario Six 
2479671f696SMario Six 	/* Range with index 2 is video memory */
2489671f696SMario Six 	priv->vidmem = regmap_get_range(priv->map, 2);
2499671f696SMario Six 
2509671f696SMario Six 	mode = dev_read_string(dev, "mode");
2519671f696SMario Six 	if (!mode) {
2529671f696SMario Six 		debug("%s: Could not read mode property\n", dev->name);
2539671f696SMario Six 		return -EINVAL;
2549671f696SMario Six 	}
2559671f696SMario Six 
2569671f696SMario Six 	if (!strcmp(mode, "1024_768_60")) {
2579671f696SMario Six 		priv->sync_src = 2;
2589671f696SMario Six 		priv->res_x = 1024;
2599671f696SMario Six 		priv->res_y = 768;
2609671f696SMario Six 		timing.hactive.typ = 1024;
2619671f696SMario Six 		timing.vactive.typ = 768;
2629671f696SMario Six 	} else if (!strcmp(mode, "720_400_70")) {
2639671f696SMario Six 		priv->sync_src = 1;
2649671f696SMario Six 		priv->res_x = 720;
2659671f696SMario Six 		priv->res_y = 400;
2669671f696SMario Six 		timing.hactive.typ = 720;
2679671f696SMario Six 		timing.vactive.typ = 400;
2689671f696SMario Six 	} else {
2699671f696SMario Six 		priv->sync_src = 0;
2709671f696SMario Six 		priv->res_x = 640;
2719671f696SMario Six 		priv->res_y = 480;
2729671f696SMario Six 		timing.hactive.typ = 640;
2739671f696SMario Six 		timing.vactive.typ = 480;
2749671f696SMario Six 	}
2759671f696SMario Six 
2769671f696SMario Six 	ihs_video_out_get(priv->map, features, &features);
2779671f696SMario Six 
2789671f696SMario Six 	res = set_control(dev, CONTROL_FILTER_ORIGINAL |
2799671f696SMario Six 			       CONTROL_MODE_OSD |
2809671f696SMario Six 			       CONTROL_ENABLE_OFF);
2819671f696SMario Six 	if (res) {
2829671f696SMario Six 		debug("%s: Could not set control register (err = %d)\n",
2839671f696SMario Six 		      dev->name, res);
2849671f696SMario Six 		return res;
2859671f696SMario Six 	}
2869671f696SMario Six 
2879671f696SMario Six 	priv->base_width = ((features & BASE_WIDTH_MASK)
2889671f696SMario Six 			    >> BASE_WIDTH_SHIFT) + 1;
2899671f696SMario Six 	priv->base_height = ((features & BASE_HEIGTH_MASK)
2909671f696SMario Six 			     >> BASE_HEIGTH_SHIFT) + 1;
2919671f696SMario Six 
2929671f696SMario Six 	res = dev_read_phandle_with_args(dev, "clk_gen", NULL, 0, 0,
2939671f696SMario Six 					 &phandle_args);
2949671f696SMario Six 	if (res) {
2959671f696SMario Six 		debug("%s: Could not get clk_gen node (err = %d)\n",
2969671f696SMario Six 		      dev->name, res);
2979671f696SMario Six 		return -EINVAL;
2989671f696SMario Six 	}
2999671f696SMario Six 
3009671f696SMario Six 	res = uclass_get_device_by_ofnode(UCLASS_CLK, phandle_args.node,
3019671f696SMario Six 					  &priv->clk_gen);
3029671f696SMario Six 	if (res) {
3039671f696SMario Six 		debug("%s: Could not get clk_gen dev (err = %d)\n",
3049671f696SMario Six 		      dev->name, res);
3059671f696SMario Six 		return -EINVAL;
3069671f696SMario Six 	}
3079671f696SMario Six 
3089671f696SMario Six 	res = dev_read_phandle_with_args(dev, "video_tx", NULL, 0, 0,
3099671f696SMario Six 					 &phandle_args);
3109671f696SMario Six 	if (res) {
3119671f696SMario Six 		debug("%s: Could not get video_tx (err = %d)\n",
3129671f696SMario Six 		      dev->name, res);
3139671f696SMario Six 		return -EINVAL;
3149671f696SMario Six 	}
3159671f696SMario Six 
3169671f696SMario Six 	res = uclass_get_device_by_ofnode(UCLASS_DISPLAY, phandle_args.node,
3179671f696SMario Six 					  &priv->video_tx);
3189671f696SMario Six 	if (res) {
3199671f696SMario Six 		debug("%s: Could not get video_tx dev (err = %d)\n",
3209671f696SMario Six 		      dev->name, res);
3219671f696SMario Six 		return -EINVAL;
3229671f696SMario Six 	}
3239671f696SMario Six 
3249671f696SMario Six 	res = display_enable(priv->video_tx, 8, &timing);
325*6df07d85SMario Six 	if (res && res != -EIO) { /* Ignore missing DP sink error */
3269671f696SMario Six 		debug("%s: Could not enable the display (err = %d)\n",
3279671f696SMario Six 		      dev->name, res);
3289671f696SMario Six 		return res;
3299671f696SMario Six 	}
3309671f696SMario Six 
3319671f696SMario Six 	return 0;
3329671f696SMario Six }
3339671f696SMario Six 
3349671f696SMario Six U_BOOT_DRIVER(ihs_video_out_drv) = {
3359671f696SMario Six 	.name           = "ihs_video_out_drv",
3369671f696SMario Six 	.id             = UCLASS_VIDEO_OSD,
3379671f696SMario Six 	.ops		= &ihs_video_out_ops,
3389671f696SMario Six 	.of_match       = ihs_video_out_ids,
3399671f696SMario Six 	.probe          = ihs_video_out_probe,
3409671f696SMario Six 	.priv_auto_alloc_size = sizeof(struct ihs_video_out_priv),
3419671f696SMario Six };
342