video_osd: Add ihs_video_out driver
[platform/kernel/u-boot.git] / drivers / video / ihs_video_out.c
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 <regmap.h>
16 #include <video_osd.h>
17 #include <asm/gpio.h>
18
19 static const uint MAX_X_CHARS = 53;
20 static const uint MAX_Y_CHARS = 26;
21 static const uint MAX_VIDEOMEM_WIDTH = 64;
22 static const uint MAX_VIDEOMEM_HEIGHT = 32;
23 static const uint CHAR_WIDTH = 12;
24 static const uint CHAR_HEIGHT = 18;
25
26 static const u16 BASE_WIDTH_MASK = 0x3f00;
27 static const uint BASE_WIDTH_SHIFT = 8;
28 static const u16 BASE_HEIGTH_MASK = 0x001f;
29 static const uint BASE_HEIGTH_SHIFT;
30
31 struct 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
54 enum {
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
69 struct 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
90 static 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  */
104 static 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
116 int 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
131 int 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  */
176 static inline u16 div2_u16(u16 val)
177 {
178         return (32767 * val) / 65535;
179 }
180
181 int 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
201 int 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
224 static 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
231 int 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);
241         if (!res) {
242                 debug("%s: Could initialize regmap (err = %d)\n", dev->name,
243                       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);
325         if (res) {
326                 debug("%s: Could not enable the display (err = %d)\n",
327                       dev->name, res);
328                 return res;
329         }
330
331         return 0;
332 }
333
334 U_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 };