Mario Six | 8ea19da | 2018-09-27 09:19:30 +0200 | [diff] [blame] | 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); |
Mario Six | d6283b9 | 2019-01-28 09:50:58 +0100 | [diff] [blame] | 241 | if (res) { |
| 242 | debug("%s: Could not initialize regmap (err = %d)\n", dev->name, |
Mario Six | 8ea19da | 2018-09-27 09:19:30 +0200 | [diff] [blame] | 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); |
Mario Six | d6283b9 | 2019-01-28 09:50:58 +0100 | [diff] [blame] | 325 | if (res && res != -EIO) { /* Ignore missing DP sink error */ |
Mario Six | 8ea19da | 2018-09-27 09:19:30 +0200 | [diff] [blame] | 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 | }; |