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 
15 DECLARE_GLOBAL_DATA_PTR;
16 
17 /* Parse atags provided by Samsung bootloader to get available memory */
18 static ulong fw_mach __section(".data");
19 static ulong fw_atags __section(".data");
20 
21 static const struct tag *fw_atags_copy;
22 static uint fw_atags_size;
23 
save_boot_params(ulong r0,ulong r1,ulong r2,ulong r3)24 void 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 
fw_atags_get(void)31 static 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 
dram_init(void)43 int 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 
dram_init_banksize(void)61 int 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 
board_init(void)81 int 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 
parse_serial(const struct tag_serialnr * serialnr)88 static 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="
101 static ulong board_id = 0;
102 static ulong lcdtype = 0;
103 
parse_cmdline(const struct tag_cmdline * cmdline)104 static 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  */
skip_atag(u32 tag)135 static inline bool skip_atag(u32 tag)
136 {
137 	return (tag == ATAG_NONE || tag == ATAG_CORE ||
138 		tag == ATAG_INITRD || tag == ATAG_INITRD2);
139 }
140 
copy_atags(const struct tag * tags)141 static 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 
misc_init_r(void)181 int misc_init_r(void)
182 {
183 	copy_atags(fw_atags_get());
184 	return 0;
185 }
186 
setup_board_tags(struct tag ** in_params)187 void 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 
codina_patch_display(void * fdt)206 static 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 
skomer_kyle_patch_display(void * fdt)233 static 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 
ft_board_setup(void * fdt,struct bd_info * bd)265 int 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