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