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 not 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 && res != -EIO) { /* Ignore missing DP sink error */ 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 }; 342