1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * (C) Copyright 2017
4  * Mario Six, Guntermann & Drunck GmbH, mario.six@gdsys.cc
5  *
6  * based on the gdsys osd driver, which is
7  *
8  * (C) Copyright 2010
9  * Dirk Eibach, Guntermann & Drunck GmbH, dirk.eibach@gdsys.de
10  */
11 
12 #include <common.h>
13 #include <display.h>
14 #include <dm.h>
15 #include <log.h>
16 #include <regmap.h>
17 #include <video_osd.h>
18 #include <asm/gpio.h>
19 
20 static const uint MAX_X_CHARS = 53;
21 static const uint MAX_Y_CHARS = 26;
22 static const uint MAX_VIDEOMEM_WIDTH = 64;
23 static const uint MAX_VIDEOMEM_HEIGHT = 32;
24 static const uint CHAR_WIDTH = 12;
25 static const uint CHAR_HEIGHT = 18;
26 
27 static const u16 BASE_WIDTH_MASK = 0x3f00;
28 static const uint BASE_WIDTH_SHIFT = 8;
29 static const u16 BASE_HEIGTH_MASK = 0x001f;
30 static const uint BASE_HEIGTH_SHIFT;
31 
32 struct ihs_video_out_regs {
33 	/* Device version register */
34 	u16 versions;
35 	/* Device feature register */
36 	u16 features;
37 	/* Device control register */
38 	u16 control;
39 	/* Register controlling screen size */
40 	u16 xy_size;
41 	/* Register controlling screen scaling */
42 	u16 xy_scale;
43 	/* Register controlling screen x position */
44 	u16 x_pos;
45 	/* Register controlling screen y position */
46 	u16 y_pos;
47 };
48 
49 #define ihs_video_out_set(map, member, val) \
50 	regmap_range_set(map, 1, struct ihs_video_out_regs, member, val)
51 
52 #define ihs_video_out_get(map, member, valp) \
53 	regmap_range_get(map, 1, struct ihs_video_out_regs, member, valp)
54 
55 enum {
56 	CONTROL_FILTER_BLACK = (0 << 0),
57 	CONTROL_FILTER_ORIGINAL = (1 << 0),
58 	CONTROL_FILTER_DARKER = (2 << 0),
59 	CONTROL_FILTER_GRAY = (3 << 0),
60 
61 	CONTROL_MODE_PASSTHROUGH = (0 << 3),
62 	CONTROL_MODE_OSD = (1 << 3),
63 	CONTROL_MODE_AUTO = (2 << 3),
64 	CONTROL_MODE_OFF = (3 << 3),
65 
66 	CONTROL_ENABLE_OFF = (0 << 6),
67 	CONTROL_ENABLE_ON = (1 << 6),
68 };
69 
70 struct ihs_video_out_priv {
71 	/* Register map for OSD device */
72 	struct regmap *map;
73 	/* Pointer to video memory */
74 	u16 *vidmem;
75 	/* Display width in text columns */
76 	uint base_width;
77 	/* Display height in text rows */
78 	uint base_height;
79 	/* x-resolution of the display in pixels */
80 	uint res_x;
81 	/* y-resolution of the display in pixels */
82 	uint res_y;
83 	/* OSD's sync mode (resolution + frequency) */
84 	int sync_src;
85 	/* The display port output for this OSD */
86 	struct udevice *video_tx;
87 	/* The pixel clock generator for the display */
88 	struct udevice *clk_gen;
89 };
90 
91 static const struct udevice_id ihs_video_out_ids[] = {
92 	{ .compatible = "gdsys,ihs_video_out" },
93 	{ }
94 };
95 
96 /**
97  * set_control() - Set the control register to a given value
98  *
99  * The current value of sync_src is preserved by the function automatically.
100  *
101  * @dev: the OSD device whose control register to set
102  * @value: the 16-bit value to write to the control register
103  * Return: 0
104  */
set_control(struct udevice * dev,u16 value)105 static int set_control(struct udevice *dev, u16 value)
106 {
107 	struct ihs_video_out_priv *priv = dev_get_priv(dev);
108 
109 	if (priv->sync_src)
110 		value |= ((priv->sync_src & 0x7) << 8);
111 
112 	ihs_video_out_set(priv->map, control, value);
113 
114 	return 0;
115 }
116 
ihs_video_out_get_info(struct udevice * dev,struct video_osd_info * info)117 int ihs_video_out_get_info(struct udevice *dev, struct video_osd_info *info)
118 {
119 	struct ihs_video_out_priv *priv = dev_get_priv(dev);
120 	u16 versions;
121 
122 	ihs_video_out_get(priv->map, versions, &versions);
123 
124 	info->width = priv->base_width;
125 	info->height = priv->base_height;
126 	info->major_version = versions / 100;
127 	info->minor_version = versions % 100;
128 
129 	return 0;
130 }
131 
ihs_video_out_set_mem(struct udevice * dev,uint col,uint row,u8 * buf,size_t buflen,uint count)132 int ihs_video_out_set_mem(struct udevice *dev, uint col, uint row, u8 *buf,
133 			  size_t buflen, uint count)
134 {
135 	struct ihs_video_out_priv *priv = dev_get_priv(dev);
136 	int res;
137 	uint offset;
138 	uint k, rep;
139 	u16 data;
140 
141 	/* Repetitions (controlled via count parmeter) */
142 	for (rep = 0; rep < count; ++rep) {
143 		offset = row * priv->base_width + col + rep * (buflen / 2);
144 
145 		/* Write a single buffer copy */
146 		for (k = 0; k < buflen / 2; ++k) {
147 			uint max_size = priv->base_width * priv->base_height;
148 
149 			if (offset + k >= max_size) {
150 				debug("%s: Write would be out of OSD bounds\n",
151 				      dev->name);
152 				return -E2BIG;
153 			}
154 
155 			data = buf[2 * k + 1] + 256 * buf[2 * k];
156 			out_le16(priv->vidmem + offset + k, data);
157 		}
158 	}
159 
160 	res = set_control(dev, CONTROL_FILTER_ORIGINAL |
161 			       CONTROL_MODE_OSD |
162 			       CONTROL_ENABLE_ON);
163 	if (res) {
164 		debug("%s: Could not set control register\n", dev->name);
165 		return res;
166 	}
167 
168 	return 0;
169 }
170 
171 /**
172  * div2_u16() - Approximately divide a 16-bit number by 2
173  *
174  * @val: The 16-bit value to divide by two
175  * Return: The approximate division of val by two
176  */
div2_u16(u16 val)177 static inline u16 div2_u16(u16 val)
178 {
179 	return (32767 * val) / 65535;
180 }
181 
ihs_video_out_set_size(struct udevice * dev,uint col,uint row)182 int ihs_video_out_set_size(struct udevice *dev, uint col, uint row)
183 {
184 	struct ihs_video_out_priv *priv = dev_get_priv(dev);
185 
186 	if (!col || col > MAX_VIDEOMEM_WIDTH || col > MAX_X_CHARS ||
187 	    !row || row > MAX_VIDEOMEM_HEIGHT || row > MAX_Y_CHARS) {
188 		debug("%s: Desired OSD size invalid\n", dev->name);
189 		return -EINVAL;
190 	}
191 
192 	ihs_video_out_set(priv->map, xy_size, ((col - 1) << 8) | (row - 1));
193 	/* Center OSD on screen */
194 	ihs_video_out_set(priv->map, x_pos,
195 			  div2_u16(priv->res_x - CHAR_WIDTH * col));
196 	ihs_video_out_set(priv->map, y_pos,
197 			  div2_u16(priv->res_y - CHAR_HEIGHT * row));
198 
199 	return 0;
200 }
201 
ihs_video_out_print(struct udevice * dev,uint col,uint row,ulong color,char * text)202 int ihs_video_out_print(struct udevice *dev, uint col, uint row, ulong color,
203 			char *text)
204 {
205 	int res;
206 	u8 buffer[2 * MAX_VIDEOMEM_WIDTH];
207 	uint k;
208 	uint charcount = strlen(text);
209 	uint len = min(charcount, 2 * MAX_VIDEOMEM_WIDTH);
210 
211 	for (k = 0; k < len; ++k) {
212 		buffer[2 * k] = text[k];
213 		buffer[2 * k + 1] = color;
214 	}
215 
216 	res = ihs_video_out_set_mem(dev, col, row, buffer, 2 * len, 1);
217 	if (res < 0) {
218 		debug("%s: Could not write to video memory\n", dev->name);
219 		return res;
220 	}
221 
222 	return 0;
223 }
224 
225 static const struct video_osd_ops ihs_video_out_ops = {
226 	.get_info = ihs_video_out_get_info,
227 	.set_mem = ihs_video_out_set_mem,
228 	.set_size = ihs_video_out_set_size,
229 	.print = ihs_video_out_print,
230 };
231 
ihs_video_out_probe(struct udevice * dev)232 int ihs_video_out_probe(struct udevice *dev)
233 {
234 	struct ihs_video_out_priv *priv = dev_get_priv(dev);
235 	struct ofnode_phandle_args phandle_args;
236 	const char *mode;
237 	u16 features;
238 	struct display_timing timing;
239 	int res;
240 
241 	res = regmap_init_mem(dev_ofnode(dev), &priv->map);
242 	if (res) {
243 		debug("%s: Could not initialize regmap (err = %d)\n", dev->name,
244 		      res);
245 		return res;
246 	}
247 
248 	/* Range with index 2 is video memory */
249 	priv->vidmem = regmap_get_range(priv->map, 2);
250 
251 	mode = dev_read_string(dev, "mode");
252 	if (!mode) {
253 		debug("%s: Could not read mode property\n", dev->name);
254 		return -EINVAL;
255 	}
256 
257 	if (!strcmp(mode, "1024_768_60")) {
258 		priv->sync_src = 2;
259 		priv->res_x = 1024;
260 		priv->res_y = 768;
261 		timing.hactive.typ = 1024;
262 		timing.vactive.typ = 768;
263 	} else if (!strcmp(mode, "720_400_70")) {
264 		priv->sync_src = 1;
265 		priv->res_x = 720;
266 		priv->res_y = 400;
267 		timing.hactive.typ = 720;
268 		timing.vactive.typ = 400;
269 	} else {
270 		priv->sync_src = 0;
271 		priv->res_x = 640;
272 		priv->res_y = 480;
273 		timing.hactive.typ = 640;
274 		timing.vactive.typ = 480;
275 	}
276 
277 	ihs_video_out_get(priv->map, features, &features);
278 
279 	res = set_control(dev, CONTROL_FILTER_ORIGINAL |
280 			       CONTROL_MODE_OSD |
281 			       CONTROL_ENABLE_OFF);
282 	if (res) {
283 		debug("%s: Could not set control register (err = %d)\n",
284 		      dev->name, res);
285 		return res;
286 	}
287 
288 	priv->base_width = ((features & BASE_WIDTH_MASK)
289 			    >> BASE_WIDTH_SHIFT) + 1;
290 	priv->base_height = ((features & BASE_HEIGTH_MASK)
291 			     >> BASE_HEIGTH_SHIFT) + 1;
292 
293 	res = dev_read_phandle_with_args(dev, "clk_gen", NULL, 0, 0,
294 					 &phandle_args);
295 	if (res) {
296 		debug("%s: Could not get clk_gen node (err = %d)\n",
297 		      dev->name, res);
298 		return -EINVAL;
299 	}
300 
301 	res = uclass_get_device_by_ofnode(UCLASS_CLK, phandle_args.node,
302 					  &priv->clk_gen);
303 	if (res) {
304 		debug("%s: Could not get clk_gen dev (err = %d)\n",
305 		      dev->name, res);
306 		return -EINVAL;
307 	}
308 
309 	res = dev_read_phandle_with_args(dev, "video_tx", NULL, 0, 0,
310 					 &phandle_args);
311 	if (res) {
312 		debug("%s: Could not get video_tx (err = %d)\n",
313 		      dev->name, res);
314 		return -EINVAL;
315 	}
316 
317 	res = uclass_get_device_by_ofnode(UCLASS_DISPLAY, phandle_args.node,
318 					  &priv->video_tx);
319 	if (res) {
320 		debug("%s: Could not get video_tx dev (err = %d)\n",
321 		      dev->name, res);
322 		return -EINVAL;
323 	}
324 
325 	res = display_enable(priv->video_tx, 8, &timing);
326 	if (res && res != -EIO) { /* Ignore missing DP sink error */
327 		debug("%s: Could not enable the display (err = %d)\n",
328 		      dev->name, res);
329 		return res;
330 	}
331 
332 	return 0;
333 }
334 
335 U_BOOT_DRIVER(ihs_video_out_drv) = {
336 	.name           = "ihs_video_out_drv",
337 	.id             = UCLASS_VIDEO_OSD,
338 	.ops		= &ihs_video_out_ops,
339 	.of_match       = ihs_video_out_ids,
340 	.probe          = ihs_video_out_probe,
341 	.priv_auto	= sizeof(struct ihs_video_out_priv),
342 };
343