1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * Copyright (C) 2019 Stephan Gerhold <stephan@gerhold.net> 4 */ 5#include <common.h> 6#include <env.h> 7#include <fdt_support.h> 8#include <init.h> 9#include <log.h> 10#include <stdlib.h> 11#include <asm/global_data.h> 12#include <asm/setup.h> 13#include <asm/system.h> 14 15DECLARE_GLOBAL_DATA_PTR; 16 17/* Parse atags provided by Samsung bootloader to get available memory */ 18static ulong fw_mach __section(".data"); 19static ulong fw_atags __section(".data"); 20 21static const struct tag *fw_atags_copy; 22static uint fw_atags_size; 23 24void save_boot_params(ulong r0, ulong r1, ulong r2, ulong r3) 25{ 26 fw_mach = r1; 27 fw_atags = r2; 28 save_boot_params_ret(); 29} 30 31static const struct tag *fw_atags_get(void) 32{ 33 const struct tag *tags = (const struct tag *)fw_atags; 34 35 if (tags->hdr.tag != ATAG_CORE) { 36 log_err("Invalid atags: tag 0x%x at %p\n", tags->hdr.tag, tags); 37 return NULL; 38 } 39 40 return tags; 41} 42 43int dram_init(void) 44{ 45 const struct tag *t, *tags = fw_atags_get(); 46 47 if (!tags) 48 return -EINVAL; 49 50 for_each_tag(t, tags) { 51 if (t->hdr.tag != ATAG_MEM) 52 continue; 53 54 debug("Memory: %#x-%#x (size %#x)\n", t->u.mem.start, 55 t->u.mem.start + t->u.mem.size, t->u.mem.size); 56 gd->ram_size += t->u.mem.size; 57 } 58 return 0; 59} 60 61int dram_init_banksize(void) 62{ 63 const struct tag *t, *tags = fw_atags_get(); 64 unsigned int bank = 0; 65 66 if (!tags) 67 return -EINVAL; 68 69 for_each_tag(t, tags) { 70 if (t->hdr.tag != ATAG_MEM) 71 continue; 72 73 gd->bd->bi_dram[bank].start = t->u.mem.start; 74 gd->bd->bi_dram[bank].size = t->u.mem.size; 75 if (++bank == CONFIG_NR_DRAM_BANKS) 76 break; 77 } 78 return 0; 79} 80 81int board_init(void) 82{ 83 gd->bd->bi_arch_number = fw_mach; 84 gd->bd->bi_boot_params = fw_atags; 85 return 0; 86} 87 88static void parse_serial(const struct tag_serialnr *serialnr) 89{ 90 char serial[17]; 91 92 if (env_get("serial#")) 93 return; 94 95 sprintf(serial, "%08x%08x", serialnr->high, serialnr->low); 96 env_set("serial#", serial); 97} 98 99#define SBL_BOARD "board_id=" 100#define SBL_LCDTYPE "lcdtype=" 101static ulong board_id = 0; 102static ulong lcdtype = 0; 103 104static void parse_cmdline(const struct tag_cmdline *cmdline) 105{ 106 char *buf; 107 108 /* Export this to sbl_cmdline (secondary boot loader command line) */ 109 env_set("sbl_cmdline", cmdline->cmdline); 110 111 buf = strstr(cmdline->cmdline, SBL_BOARD); 112 if (!buf) 113 return; 114 buf += strlen(SBL_BOARD); 115 116 board_id = simple_strtoul(buf, NULL, 10); 117 118 buf = strstr(cmdline->cmdline, SBL_LCDTYPE); 119 if (!buf) 120 return; 121 buf += strlen(SBL_LCDTYPE); 122 123 lcdtype = simple_strtoul(buf, NULL, 10); 124} 125 126/* 127 * The downstream/vendor kernel (provided by Samsung) uses ATAGS for booting. 128 * It also requires an extremely long cmdline provided by the primary bootloader 129 * that is not suitable for booting mainline. 130 * 131 * Since downstream is the only user of ATAGS, we emulate the behavior of the 132 * Samsung bootloader by generating only the initrd atag in U-Boot, and copying 133 * all other ATAGS as-is from the primary bootloader. 134 */ 135static inline bool skip_atag(u32 tag) 136{ 137 return (tag == ATAG_NONE || tag == ATAG_CORE || 138 tag == ATAG_INITRD || tag == ATAG_INITRD2); 139} 140 141static void copy_atags(const struct tag *tags) 142{ 143 const struct tag *t; 144 struct tag *copy; 145 146 if (!tags) 147 return; 148 149 /* Calculate necessary size for tags we want to copy */ 150 for_each_tag(t, tags) { 151 if (skip_atag(t->hdr.tag)) 152 continue; 153 154 if (t->hdr.tag == ATAG_SERIAL) 155 parse_serial(&t->u.serialnr); 156 157 if (t->hdr.tag == ATAG_CMDLINE) 158 parse_cmdline(&t->u.cmdline); 159 160 fw_atags_size += t->hdr.size * sizeof(u32); 161 } 162 163 if (!fw_atags_size) 164 return; /* No tags to copy */ 165 166 copy = malloc(fw_atags_size); 167 if (!copy) 168 return; 169 fw_atags_copy = copy; 170 171 /* Copy tags */ 172 for_each_tag(t, tags) { 173 if (skip_atag(t->hdr.tag)) 174 continue; 175 176 memcpy(copy, t, t->hdr.size * sizeof(u32)); 177 copy = tag_next(copy); 178 } 179} 180 181int misc_init_r(void) 182{ 183 copy_atags(fw_atags_get()); 184 return 0; 185} 186 187void setup_board_tags(struct tag **in_params) 188{ 189 if (!fw_atags_copy) 190 return; 191 192 /* 193 * fw_atags_copy contains only full "struct tag" (plus data) 194 * so copying it bytewise here should be fine. 195 */ 196 memcpy(*in_params, fw_atags_copy, fw_atags_size); 197 *(u8 **)in_params += fw_atags_size; 198} 199 200/* These numbers are unique per product but not across all products */ 201#define SAMSUNG_CODINA_LCD_LMS380KF01 4 202#define SAMSUNG_CODINA_LCD_S6D27A1 13 203#define SAMSUNG_SKOMER_LCD_HVA40WV1 10 204#define SAMSUNG_SKOMER_LCD_NT35512 12 205 206static void codina_patch_display(void *fdt) 207{ 208 int node; 209 int ret; 210 211 node = fdt_path_offset(fdt, "/spi-gpio-0/panel"); 212 if (node < 0) { 213 printf("cannot find Codina panel node\n"); 214 return; 215 } 216 if (lcdtype == SAMSUNG_CODINA_LCD_LMS380KF01) { 217 ret = fdt_setprop_string(fdt, node, "compatible", "samsung,lms380kf01"); 218 if (ret < 0) 219 printf("could not set LCD compatible\n"); 220 else 221 printf("updated LCD compatible to LMS380KF01\n"); 222 } else if (lcdtype == SAMSUNG_CODINA_LCD_S6D27A1) { 223 ret = fdt_setprop_string(fdt, node, "compatible", "samsung,s6d27a1"); 224 if (ret < 0) 225 printf("could not set LCD compatible\n"); 226 else 227 printf("updated LCD compatible to S6D27A1\n"); 228 } else { 229 printf("unknown LCD type\n"); 230 } 231} 232 233static void skomer_kyle_patch_display(void *fdt) 234{ 235 int node; 236 int ret; 237 238 node = fdt_path_offset(fdt, "/soc/mcde/dsi/panel"); 239 if (node < 0) { 240 printf("cannot find Skomer/Kyle panel node\n"); 241 return; 242 } 243 if (lcdtype == SAMSUNG_SKOMER_LCD_HVA40WV1) { 244 ret = fdt_setprop_string(fdt, node, "compatible", "hydis,hva40wv1"); 245 if (ret < 0) 246 printf("could not set LCD compatible\n"); 247 else 248 printf("updated LCD compatible to Hydis HVA40WV1\n"); 249 } else if (lcdtype == SAMSUNG_SKOMER_LCD_NT35512) { 250 /* 251 * FIXME: This panel is actually a BOE product, but we don't know 252 * the exact product name, so the compatible for the NT35512 253 * is used for the time being. The vendor drivers also call it NT35512. 254 */ 255 ret = fdt_setprop_string(fdt, node, "compatible", "novatek,nt35512"); 256 if (ret < 0) 257 printf("could not set LCD compatible\n"); 258 else 259 printf("updated LCD compatible to Novatek NT35512\n"); 260 } else { 261 printf("unknown LCD type\n"); 262 } 263} 264 265int ft_board_setup(void *fdt, struct bd_info *bd) 266{ 267 const char *str; 268 int node; 269 int ret; 270 271 printf("stemmy patch: DTB at 0x%08lx\n", (ulong)fdt); 272 273 /* Inspect FDT to see what we've got here */ 274 ret = fdt_check_header(fdt); 275 if (ret < 0) { 276 printf("invalid DTB\n"); 277 return ret; 278 } 279 node = fdt_path_offset(fdt, "/"); 280 if (node < 0) { 281 printf("cannot find root node\n"); 282 return node; 283 } 284 str = fdt_stringlist_get(fdt, node, "compatible", 0, NULL); 285 if (!str) { 286 printf("could not find board compatible\n"); 287 return -1; 288 } 289 290 if (!strcmp(str, "samsung,janice")) { 291 switch(board_id) { 292 case 7: 293 printf("Janice GT-I9070 Board Rev 0.0\n"); 294 break; 295 case 8: 296 printf("Janice GT-I9070 Board Rev 0.1\n"); 297 break; 298 case 9: 299 printf("Janice GT-I9070 Board Rev 0.2\n"); 300 break; 301 case 10: 302 printf("Janice GT-I9070 Board Rev 0.3\n"); 303 break; 304 case 11: 305 printf("Janice GT-I9070 Board Rev 0.4\n"); 306 break; 307 case 12: 308 printf("Janice GT-I9070 Board Rev 0.5\n"); 309 break; 310 case 13: 311 printf("Janice GT-I9070 Board Rev 0.6\n"); 312 break; 313 default: 314 break; 315 } 316 } else if (!strcmp(str, "samsung,gavini")) { 317 switch(board_id) { 318 case 7: 319 printf("Gavini GT-I8530 Board Rev 0.0\n"); 320 break; 321 case 8: 322 printf("Gavini GT-I8530 Board Rev 0.0A\n"); 323 break; 324 case 9: 325 printf("Gavini GT-I8530 Board Rev 0.0B\n"); 326 break; 327 case 10: 328 printf("Gavini GT-I8530 Board Rev 0.0A_EMUL\n"); 329 break; 330 case 11: 331 printf("Gavini GT-I8530 Board Rev 0.0C\n"); 332 break; 333 case 12: 334 printf("Gavini GT-I8530 Board Rev 0.0D\n"); 335 break; 336 case 13: 337 printf("Gavini GT-I8530 Board Rev 0.1\n"); 338 break; 339 case 14: 340 printf("Gavini GT-I8530 Board Rev 0.3\n"); 341 break; 342 default: 343 break; 344 } 345 } else if (!strcmp(str, "samsung,codina")) { 346 switch(board_id) { 347 case 7: 348 printf("Codina GT-I8160 Board Rev 0.0\n"); 349 break; 350 case 8: 351 printf("Codina GT-I8160 Board Rev 0.1\n"); 352 break; 353 case 9: 354 printf("Codina GT-I8160 Board Rev 0.2\n"); 355 break; 356 case 10: 357 printf("Codina GT-I8160 Board Rev 0.3\n"); 358 break; 359 case 11: 360 printf("Codina GT-I8160 Board Rev 0.4\n"); 361 break; 362 case 12: 363 printf("Codina GT-I8160 Board Rev 0.5\n"); 364 break; 365 default: 366 break; 367 } 368 codina_patch_display(fdt); 369 } else if (!strcmp(str, "samsung,codina-tmo")) { 370 switch(board_id) { 371 case 0x101: 372 printf("Codina SGH-T599 Board pre-Rev 0.0\n"); 373 break; 374 case 0x102: 375 printf("Codina SGH-T599 Board Rev 0.0\n"); 376 break; 377 case 0x103: 378 printf("Codina SGH-T599 Board Rev 0.1\n"); 379 break; 380 case 0x104: 381 printf("Codina SGH-T599 Board Rev 0.2\n"); 382 break; 383 case 0x105: 384 printf("Codina SGH-T599 Board Rev 0.4\n"); 385 break; 386 case 0x106: 387 printf("Codina SGH-T599 Board Rev 0.6\n"); 388 break; 389 case 0x107: 390 printf("Codina SGH-T599 Board Rev 0.7\n"); 391 break; 392 default: 393 break; 394 } 395 codina_patch_display(fdt); 396 } else if (!strcmp(str, "samsung,golden")) { 397 switch(board_id) { 398 case 0x102: 399 printf("Golden GT-I8190 Board SW bringup\n"); 400 break; 401 case 0x103: 402 printf("Golden GT-I8190 Board Rev 0.2\n"); 403 break; 404 case 0x104: 405 printf("Golden GT-I8190 Board Rev 0.3\n"); 406 break; 407 case 0x105: 408 printf("Golden GT-I8190 Board Rev 0.4\n"); 409 break; 410 case 0x106: 411 printf("Golden GT-I8190 Board Rev 0.5\n"); 412 break; 413 case 0x107: 414 printf("Golden GT-I8190 Board Rev 0.6\n"); 415 break; 416 default: 417 break; 418 } 419 } else if (!strcmp(str, "samsung,skomer")) { 420 switch(board_id) { 421 case 0x101: 422 printf("Skomer GT-S7710 Board Rev 0.0\n"); 423 break; 424 case 0x102: 425 printf("Skomer GT-S7710 Board Rev 0.1\n"); 426 break; 427 case 0x103: 428 printf("Skomer GT-S7710 Board Rev 0.2\n"); 429 break; 430 case 0x104: 431 printf("Skomer GT-S7710 Board Rev 0.3\n"); 432 break; 433 case 0x105: 434 printf("Skomer GT-S7710 Board Rev 0.4\n"); 435 break; 436 case 0x106: 437 printf("Skomer GT-S7710 Board Rev 0.5\n"); 438 break; 439 case 0x107: 440 printf("Skomer GT-S7710 Board Rev 0.6\n"); 441 break; 442 case 0x108: 443 printf("Skomer GT-S7710 Board Rev 0.7\n"); 444 break; 445 case 0x109: 446 printf("Skomer GT-S7710 Board Rev 0.8\n"); 447 break; 448 default: 449 break; 450 } 451 skomer_kyle_patch_display(fdt); 452 } else if (!strcmp(str, "samsung,kyle")) { 453 switch(board_id) { 454 case 0x101: 455 printf("Kyle SGH-I407 Board Rev 0.0\n"); 456 break; 457 case 0x102: 458 printf("Kyle SGH-I407 Board Rev 0.1\n"); 459 break; 460 case 0x103: 461 printf("Kyle SGH-I407 Board Rev 0.2\n"); 462 break; 463 case 0x104: 464 printf("Kyle SGH-I407 Board Rev 0.3\n"); 465 break; 466 case 0x105: 467 printf("Kyle SGH-I407 Board Rev 0.4\n"); 468 break; 469 case 0x106: 470 printf("Kyle SGH-I407 Board Rev 0.5\n"); 471 break; 472 case 0x107: 473 printf("Kyle SGH-I407 Board Rev 0.6\n"); 474 break; 475 default: 476 break; 477 } 478 skomer_kyle_patch_display(fdt); 479 } 480 481 return 0; 482} 483