138771996SKever Yang /* 238771996SKever Yang * (C) Copyright 2017 Rockchip Electronics Co., Ltd. 338771996SKever Yang * 438771996SKever Yang * SPDX-License-Identifier: GPL-2.0+ 538771996SKever Yang */ 638771996SKever Yang #include <common.h> 738771996SKever Yang #include <clk.h> 838771996SKever Yang #include <dm.h> 9575777c5SKever Yang #include <debug_uart.h> 1038771996SKever Yang #include <ram.h> 1138771996SKever Yang #include <syscon.h> 1238771996SKever Yang #include <asm/io.h> 13064b2675SJason Zhu #include <asm/arch/vendor.h> 14064b2675SJason Zhu #include <misc.h> 1538771996SKever Yang #include <asm/gpio.h> 1638771996SKever Yang #include <asm/arch/clock.h> 1738771996SKever Yang #include <asm/arch/periph.h> 1838771996SKever Yang #include <asm/arch/boot_mode.h> 19064eb493SJoseph Chen #include <asm/arch/rk_atags.h> 2038771996SKever Yang #ifdef CONFIG_DM_CHARGE_DISPLAY 2138771996SKever Yang #include <power/charge_display.h> 2238771996SKever Yang #endif 2338771996SKever Yang #ifdef CONFIG_DM_REGULATOR 2438771996SKever Yang #include <power/regulator.h> 2538771996SKever Yang #endif 2638771996SKever Yang #ifdef CONFIG_DRM_ROCKCHIP 2738771996SKever Yang #include <video_rockchip.h> 2838771996SKever Yang #endif 29c563adc7SJoseph Chen #ifdef CONFIG_ROCKCHIP_DEBUGGER 30c563adc7SJoseph Chen #include <rockchip_debugger.h> 31c563adc7SJoseph Chen #endif 3298ff9f07SJoseph Chen #ifdef CONFIG_DM_RAMDISK 3398ff9f07SJoseph Chen #include <ramdisk.h> 3498ff9f07SJoseph Chen #endif 35f8aaa2c2SKever Yang #include <mmc.h> 36f8aaa2c2SKever Yang #include <of_live.h> 37f8aaa2c2SKever Yang #include <dm/root.h> 3838771996SKever Yang 3938771996SKever Yang DECLARE_GLOBAL_DATA_PTR; 40064b2675SJason Zhu /* define serialno max length, the max length is 512 Bytes 41064b2675SJason Zhu * The remaining bytes are used to ensure that the first 512 bytes 42064b2675SJason Zhu * are valid when executing 'env_set("serial#", value)'. 43064b2675SJason Zhu */ 44064b2675SJason Zhu #define VENDOR_SN_MAX 513 45064b2675SJason Zhu #define CPUID_LEN 0x10 46064b2675SJason Zhu #define CPUID_OFF 0x7 47064b2675SJason Zhu 48064b2675SJason Zhu static int rockchip_set_serialno(void) 49064b2675SJason Zhu { 50064b2675SJason Zhu char serialno_str[VENDOR_SN_MAX]; 51064b2675SJason Zhu int ret = 0, i; 52064b2675SJason Zhu u8 cpuid[CPUID_LEN] = {0}; 53064b2675SJason Zhu u8 low[CPUID_LEN / 2], high[CPUID_LEN / 2]; 54064b2675SJason Zhu u64 serialno; 55064b2675SJason Zhu 56064b2675SJason Zhu /* Read serial number from vendor storage part */ 57064b2675SJason Zhu memset(serialno_str, 0, VENDOR_SN_MAX); 58064b2675SJason Zhu #ifdef CONFIG_ROCKCHIP_VENDOR_PARTITION 59064b2675SJason Zhu ret = vendor_storage_read(VENDOR_SN_ID, serialno_str, (VENDOR_SN_MAX-1)); 60064b2675SJason Zhu if (ret > 0) { 61064b2675SJason Zhu env_set("serial#", serialno_str); 62064b2675SJason Zhu } else { 63064b2675SJason Zhu #endif 64064b2675SJason Zhu #ifdef CONFIG_ROCKCHIP_EFUSE 65064b2675SJason Zhu struct udevice *dev; 66064b2675SJason Zhu 67064b2675SJason Zhu /* retrieve the device */ 68064b2675SJason Zhu ret = uclass_get_device_by_driver(UCLASS_MISC, 69064b2675SJason Zhu DM_GET_DRIVER(rockchip_efuse), &dev); 70064b2675SJason Zhu if (ret) { 71064b2675SJason Zhu printf("%s: could not find efuse device\n", __func__); 72064b2675SJason Zhu return ret; 73064b2675SJason Zhu } 74064b2675SJason Zhu /* read the cpu_id range from the efuses */ 75064b2675SJason Zhu ret = misc_read(dev, CPUID_OFF, &cpuid, sizeof(cpuid)); 76064b2675SJason Zhu if (ret) { 77064b2675SJason Zhu printf("%s: reading cpuid from the efuses failed\n", __func__); 78064b2675SJason Zhu return ret; 79064b2675SJason Zhu } 80064b2675SJason Zhu #else 81064b2675SJason Zhu /* generate random cpuid */ 82064b2675SJason Zhu for (i = 0; i < CPUID_LEN; i++) { 83064b2675SJason Zhu cpuid[i] = (u8)(rand()); 84064b2675SJason Zhu } 85064b2675SJason Zhu #endif 86064b2675SJason Zhu /* Generate the serial number based on CPU ID */ 87064b2675SJason Zhu for (i = 0; i < 8; i++) { 88064b2675SJason Zhu low[i] = cpuid[1 + (i << 1)]; 89064b2675SJason Zhu high[i] = cpuid[i << 1]; 90064b2675SJason Zhu } 91064b2675SJason Zhu serialno = crc32_no_comp(0, low, 8); 92064b2675SJason Zhu serialno |= (u64)crc32_no_comp(serialno, high, 8) << 32; 93064b2675SJason Zhu snprintf(serialno_str, sizeof(serialno_str), "%llx", serialno); 94064b2675SJason Zhu 95064b2675SJason Zhu env_set("serial#", serialno_str); 96064b2675SJason Zhu #ifdef CONFIG_ROCKCHIP_VENDOR_PARTITION 97064b2675SJason Zhu } 98064b2675SJason Zhu #endif 99064b2675SJason Zhu return ret; 100064b2675SJason Zhu } 10138771996SKever Yang 10238771996SKever Yang #if defined(CONFIG_USB_FUNCTION_FASTBOOT) 10338771996SKever Yang int fb_set_reboot_flag(void) 10438771996SKever Yang { 10538771996SKever Yang printf("Setting reboot to fastboot flag ...\n"); 10638771996SKever Yang /* Set boot mode to fastboot */ 10738771996SKever Yang writel(BOOT_FASTBOOT, CONFIG_ROCKCHIP_BOOT_MODE_REG); 10838771996SKever Yang 10938771996SKever Yang return 0; 11038771996SKever Yang } 11138771996SKever Yang #endif 11238771996SKever Yang 11338771996SKever Yang #ifdef CONFIG_DM_CHARGE_DISPLAY 11438771996SKever Yang static int charge_display(void) 11538771996SKever Yang { 11638771996SKever Yang int ret; 11738771996SKever Yang struct udevice *dev; 11838771996SKever Yang 11938771996SKever Yang ret = uclass_get_device(UCLASS_CHARGE_DISPLAY, 0, &dev); 12038771996SKever Yang if (ret) { 12138771996SKever Yang if (ret != -ENODEV) { 122bb2992faSJoseph Chen debug("Get UCLASS CHARGE DISPLAY failed: %d\n", ret); 12338771996SKever Yang return ret; 124eeb3338cSJoseph Chen } else { 125eeb3338cSJoseph Chen debug("Can't find charge display driver\n"); 12638771996SKever Yang } 12738771996SKever Yang return 0; 12838771996SKever Yang } 12938771996SKever Yang 13038771996SKever Yang return charge_display_show(dev); 13138771996SKever Yang } 13238771996SKever Yang #endif 13338771996SKever Yang 13438771996SKever Yang __weak int rk_board_init(void) 13538771996SKever Yang { 13638771996SKever Yang return 0; 13738771996SKever Yang } 13838771996SKever Yang 13938771996SKever Yang __weak int rk_board_late_init(void) 14038771996SKever Yang { 14138771996SKever Yang return 0; 14238771996SKever Yang } 14338771996SKever Yang 144efdbac34SFinley Xiao __weak int soc_clk_dump(void) 145efdbac34SFinley Xiao { 146efdbac34SFinley Xiao return 0; 147efdbac34SFinley Xiao } 148efdbac34SFinley Xiao 149058e5d94SFinley Xiao __weak int set_armclk_rate(void) 150058e5d94SFinley Xiao { 151058e5d94SFinley Xiao return 0; 152058e5d94SFinley Xiao } 153058e5d94SFinley Xiao 15438771996SKever Yang int board_late_init(void) 15538771996SKever Yang { 15638771996SKever Yang #if (CONFIG_ROCKCHIP_BOOT_MODE_REG > 0) 15738771996SKever Yang setup_boot_mode(); 15838771996SKever Yang #endif 15938771996SKever Yang 16038771996SKever Yang #ifdef CONFIG_DM_CHARGE_DISPLAY 16138771996SKever Yang charge_display(); 16238771996SKever Yang #endif 16338771996SKever Yang 16438771996SKever Yang #ifdef CONFIG_DRM_ROCKCHIP 16538771996SKever Yang rockchip_show_logo(); 16638771996SKever Yang #endif 167064b2675SJason Zhu rockchip_set_serialno(); 16838771996SKever Yang 169efdbac34SFinley Xiao soc_clk_dump(); 170efdbac34SFinley Xiao 17138771996SKever Yang return rk_board_late_init(); 17238771996SKever Yang } 17338771996SKever Yang 174f8aaa2c2SKever Yang #ifdef CONFIG_USING_KERNEL_DTB 175f8aaa2c2SKever Yang #include <asm/arch/resource_img.h> 176740107bbSJoseph Chen 177f8aaa2c2SKever Yang int init_kernel_dtb(void) 178f8aaa2c2SKever Yang { 179f8aaa2c2SKever Yang int ret = 0; 180f8aaa2c2SKever Yang ulong fdt_addr = 0; 181f8aaa2c2SKever Yang 18298ff9f07SJoseph Chen #ifdef CONFIG_DM_MMC 183f8aaa2c2SKever Yang ret = mmc_initialize(gd->bd); 184f8aaa2c2SKever Yang if (ret) 18598ff9f07SJoseph Chen debug("%s: mmc initialized failed, ret=%d\n", __func__ ,ret); 18698ff9f07SJoseph Chen #else 18798ff9f07SJoseph Chen ret = bramdisk_initialize(); 18898ff9f07SJoseph Chen if (ret) 18998ff9f07SJoseph Chen debug("%s: bramdisk initialized failed, ret=%d\n", __func__, ret); 19098ff9f07SJoseph Chen #endif 191f8aaa2c2SKever Yang 192f8aaa2c2SKever Yang fdt_addr = env_get_ulong("fdt_addr_r", 16, 0); 193f8aaa2c2SKever Yang if (!fdt_addr) { 194f8aaa2c2SKever Yang printf("No Found FDT Load Address.\n"); 195f8aaa2c2SKever Yang return -1; 196f8aaa2c2SKever Yang } 197f8aaa2c2SKever Yang 198740107bbSJoseph Chen ret = rockchip_read_dtb_file((void *)fdt_addr); 199f8aaa2c2SKever Yang if (ret < 0) { 200f8aaa2c2SKever Yang printf("%s dtb in resource read fail\n", __func__); 201f8aaa2c2SKever Yang return 0; 202f8aaa2c2SKever Yang } 203f8aaa2c2SKever Yang 204f8aaa2c2SKever Yang of_live_build((void *)fdt_addr, (struct device_node **)&gd->of_root); 205f8aaa2c2SKever Yang 206f8aaa2c2SKever Yang dm_scan_fdt((void *)fdt_addr, false); 207f8aaa2c2SKever Yang 208f8aaa2c2SKever Yang gd->fdt_blob = (void *)fdt_addr; 209f8aaa2c2SKever Yang 210a38e17c3SJoseph Chen printf("Using kernel dtb\n"); 211a38e17c3SJoseph Chen 212f8aaa2c2SKever Yang return 0; 213f8aaa2c2SKever Yang } 214f8aaa2c2SKever Yang #endif 215f8aaa2c2SKever Yang 216f8aaa2c2SKever Yang 21738771996SKever Yang int board_init(void) 21838771996SKever Yang { 21938771996SKever Yang int ret; 22038771996SKever Yang 221575777c5SKever Yang board_debug_uart_init(); 222ebb6c439SYouMin Chen 223f8aaa2c2SKever Yang #ifdef CONFIG_USING_KERNEL_DTB 224f8aaa2c2SKever Yang init_kernel_dtb(); 225f8aaa2c2SKever Yang #endif 2269f8e13d3SFinley Xiao /* 2279f8e13d3SFinley Xiao * pmucru isn't referenced on some platforms, so pmucru driver can't 2289f8e13d3SFinley Xiao * probe that the "assigned-clocks" is unused. 2299f8e13d3SFinley Xiao */ 2309f8e13d3SFinley Xiao clks_probe(); 23138771996SKever Yang #ifdef CONFIG_DM_REGULATOR 23238771996SKever Yang ret = regulators_enable_boot_on(false); 23338771996SKever Yang if (ret) 23438771996SKever Yang debug("%s: Cannot enable boot on regulator\n", __func__); 23538771996SKever Yang #endif 236058e5d94SFinley Xiao set_armclk_rate(); 23738771996SKever Yang 23838771996SKever Yang return rk_board_init(); 23938771996SKever Yang } 24038771996SKever Yang 241c563adc7SJoseph Chen int interrupt_debugger_init(void) 242c563adc7SJoseph Chen { 243c563adc7SJoseph Chen int ret = 0; 244c563adc7SJoseph Chen 245c563adc7SJoseph Chen #ifdef CONFIG_ROCKCHIP_DEBUGGER 246c563adc7SJoseph Chen ret = rockchip_debugger_init(); 247c563adc7SJoseph Chen #endif 248c563adc7SJoseph Chen return ret; 249c563adc7SJoseph Chen } 250c563adc7SJoseph Chen 251e09b1e4aSJoseph Chen int board_fdt_fixup(void *blob) 252e09b1e4aSJoseph Chen { 253e09b1e4aSJoseph Chen __maybe_unused int ret = 0; 254e09b1e4aSJoseph Chen 255e09b1e4aSJoseph Chen #ifdef CONFIG_DRM_ROCKCHIP 256e09b1e4aSJoseph Chen rockchip_display_fixup(blob); 257e09b1e4aSJoseph Chen #endif 258e09b1e4aSJoseph Chen 259e09b1e4aSJoseph Chen #ifdef CONFIG_ROCKCHIP_RK3288 260e09b1e4aSJoseph Chen /* RK3288W HDMI Revision ID is 0x1A */ 261e09b1e4aSJoseph Chen if (readl(0xff980004) == 0x1A) { 262e09b1e4aSJoseph Chen ret = fdt_setprop_string(blob, 0, 263e09b1e4aSJoseph Chen "compatible", "rockchip,rk3288w"); 264e09b1e4aSJoseph Chen if (ret) 265e09b1e4aSJoseph Chen printf("fdt set compatible failed: %d\n", ret); 266e09b1e4aSJoseph Chen } 267e09b1e4aSJoseph Chen #endif 268e09b1e4aSJoseph Chen 269e09b1e4aSJoseph Chen return ret; 270e09b1e4aSJoseph Chen } 271e09b1e4aSJoseph Chen 27238771996SKever Yang void enable_caches(void) 27338771996SKever Yang { 274567735c8SJoseph Chen icache_enable(); 27538771996SKever Yang dcache_enable(); 27638771996SKever Yang } 27738771996SKever Yang 278*2c6a058bSJoseph Chen /* 279*2c6a058bSJoseph Chen * Using last bi_dram[...] to initialize "bootm_low" and "bootm_mapsize". 280*2c6a058bSJoseph Chen * This makes lmb_alloc_base() always alloc from tail of sdram. 281*2c6a058bSJoseph Chen * If we don't assign it, bi_dram[0] is used by default and it may cause 282*2c6a058bSJoseph Chen * lmb_alloc_base() fail when bi_dram[0] range is small. 283*2c6a058bSJoseph Chen */ 284*2c6a058bSJoseph Chen void board_lmb_reserve(struct lmb *lmb) 285*2c6a058bSJoseph Chen { 286*2c6a058bSJoseph Chen u64 start, size; 287*2c6a058bSJoseph Chen char bootm_low[32]; 288*2c6a058bSJoseph Chen char bootm_mapsize[32]; 289*2c6a058bSJoseph Chen int i; 290*2c6a058bSJoseph Chen 291*2c6a058bSJoseph Chen for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) { 292*2c6a058bSJoseph Chen if (!gd->bd->bi_dram[i].size) 293*2c6a058bSJoseph Chen break; 294*2c6a058bSJoseph Chen } 295*2c6a058bSJoseph Chen 296*2c6a058bSJoseph Chen start = gd->bd->bi_dram[i - 1].start; 297*2c6a058bSJoseph Chen size = gd->bd->bi_dram[i - 1].size; 298*2c6a058bSJoseph Chen 299*2c6a058bSJoseph Chen /* 300*2c6a058bSJoseph Chen * 32-bit kernel: ramdisk/fdt shouldn't be loaded to highmem area, otherwise 301*2c6a058bSJoseph Chen * "Unable to handle kernel paging request at virtual address ...". 302*2c6a058bSJoseph Chen * If so, using low address region, i.e before tustos region(132MB). 303*2c6a058bSJoseph Chen */ 304*2c6a058bSJoseph Chen #ifndef ARM64 305*2c6a058bSJoseph Chen if (start >= ((u64)CONFIG_SYS_SDRAM_BASE + SZ_768M)) { 306*2c6a058bSJoseph Chen start = gd->bd->bi_dram[i - 2].start; 307*2c6a058bSJoseph Chen size = gd->bd->bi_dram[i - 2].size; 308*2c6a058bSJoseph Chen } 309*2c6a058bSJoseph Chen 310*2c6a058bSJoseph Chen if ((start + size) > ((u64)CONFIG_SYS_SDRAM_BASE + SZ_768M)) 311*2c6a058bSJoseph Chen size = (u64)CONFIG_SYS_SDRAM_BASE + SZ_768M - start; 312*2c6a058bSJoseph Chen #endif 313*2c6a058bSJoseph Chen sprintf(bootm_low, "0x%llx", start); 314*2c6a058bSJoseph Chen sprintf(bootm_mapsize, "0x%llx", size); 315*2c6a058bSJoseph Chen env_set("bootm_low", bootm_low); 316*2c6a058bSJoseph Chen env_set("bootm_mapsize", bootm_mapsize); 317*2c6a058bSJoseph Chen } 318*2c6a058bSJoseph Chen 319064eb493SJoseph Chen #ifdef CONFIG_ROCKCHIP_PRELOADER_SERIAL 320064eb493SJoseph Chen int board_init_f_init_serial(void) 321064eb493SJoseph Chen { 322064eb493SJoseph Chen struct tag *t = atags_get_tag(ATAG_SERIAL); 323064eb493SJoseph Chen 324064eb493SJoseph Chen if (t) { 325064eb493SJoseph Chen gd->serial.using_pre_serial = t->u.serial.enable; 326064eb493SJoseph Chen gd->serial.addr = t->u.serial.addr; 327064eb493SJoseph Chen gd->serial.baudrate = t->u.serial.baudrate; 328064eb493SJoseph Chen gd->serial.id = t->u.serial.id; 329064eb493SJoseph Chen 330064eb493SJoseph Chen debug("%s: enable=%d, addr=0x%lx, baudrate=%d, id=%d\n", 331064eb493SJoseph Chen __func__, gd->serial.using_pre_serial, 332064eb493SJoseph Chen gd->serial.addr, gd->serial.baudrate, 333064eb493SJoseph Chen gd->serial.id); 334064eb493SJoseph Chen } 335064eb493SJoseph Chen 336064eb493SJoseph Chen return 0; 337064eb493SJoseph Chen } 338064eb493SJoseph Chen #endif 339064eb493SJoseph Chen 34038771996SKever Yang #if defined(CONFIG_USB_GADGET) && defined(CONFIG_USB_GADGET_DWC2_OTG) 3416f7b6465SFrank Wang #include <fdt_support.h> 34238771996SKever Yang #include <usb.h> 34338771996SKever Yang #include <usb/dwc2_udc.h> 34438771996SKever Yang 34538771996SKever Yang static struct dwc2_plat_otg_data otg_data = { 34638771996SKever Yang .rx_fifo_sz = 512, 34738771996SKever Yang .np_tx_fifo_sz = 16, 34838771996SKever Yang .tx_fifo_sz = 128, 34938771996SKever Yang }; 35038771996SKever Yang 35138771996SKever Yang int board_usb_init(int index, enum usb_init_type init) 35238771996SKever Yang { 35338771996SKever Yang int node; 35438771996SKever Yang const char *mode; 3556f7b6465SFrank Wang fdt_addr_t addr; 3566f7b6465SFrank Wang const fdt32_t *reg; 35738771996SKever Yang bool matched = false; 35838771996SKever Yang const void *blob = gd->fdt_blob; 35938771996SKever Yang 36038771996SKever Yang /* find the usb_otg node */ 36138771996SKever Yang node = fdt_node_offset_by_compatible(blob, -1, 36238771996SKever Yang "snps,dwc2"); 36338771996SKever Yang 36438771996SKever Yang while (node > 0) { 36538771996SKever Yang mode = fdt_getprop(blob, node, "dr_mode", NULL); 36638771996SKever Yang if (mode && strcmp(mode, "otg") == 0) { 36738771996SKever Yang matched = true; 36838771996SKever Yang break; 36938771996SKever Yang } 37038771996SKever Yang 37138771996SKever Yang node = fdt_node_offset_by_compatible(blob, node, 37238771996SKever Yang "snps,dwc2"); 37338771996SKever Yang } 374e7b5bb3cSWilliam Wu 37538771996SKever Yang if (!matched) { 376e7b5bb3cSWilliam Wu /* 377e7b5bb3cSWilliam Wu * With kernel dtb support, rk3288 dwc2 otg node 378e7b5bb3cSWilliam Wu * use the rockchip legacy dwc2 driver "dwc_otg_310" 379e0b87408SJoseph Chen * with the compatible "rockchip,rk3288_usb20_otg", 380e0b87408SJoseph Chen * and rk3368 also use the "dwc_otg_310" driver with 381e0b87408SJoseph Chen * the compatible "rockchip,rk3368-usb". 382e7b5bb3cSWilliam Wu */ 383e0b87408SJoseph Chen #if defined(CONFIG_ROCKCHIP_RK3288) 384e7b5bb3cSWilliam Wu node = fdt_node_offset_by_compatible(blob, -1, 385e7b5bb3cSWilliam Wu "rockchip,rk3288_usb20_otg"); 386e0b87408SJoseph Chen #elif defined(CONFIG_ROCKCHIP_RK3368) 387e0b87408SJoseph Chen node = fdt_node_offset_by_compatible(blob, -1, 388e0b87408SJoseph Chen "rockchip,rk3368-usb"); 389e0b87408SJoseph Chen #endif 390e0b87408SJoseph Chen 391e7b5bb3cSWilliam Wu if (node > 0) { 392e7b5bb3cSWilliam Wu matched = true; 393e7b5bb3cSWilliam Wu } else { 394e7b5bb3cSWilliam Wu pr_err("Not found usb_otg device\n"); 39538771996SKever Yang return -ENODEV; 39638771996SKever Yang } 397e7b5bb3cSWilliam Wu } 3986f7b6465SFrank Wang 3996f7b6465SFrank Wang reg = fdt_getprop(blob, node, "reg", NULL); 4006f7b6465SFrank Wang if (!reg) 4016f7b6465SFrank Wang return -EINVAL; 4026f7b6465SFrank Wang 4036f7b6465SFrank Wang addr = fdt_translate_address(blob, node, reg); 4046f7b6465SFrank Wang if (addr == OF_BAD_ADDR) { 4056f7b6465SFrank Wang pr_err("Not found usb_otg address\n"); 4066f7b6465SFrank Wang return -EINVAL; 4076f7b6465SFrank Wang } 4086f7b6465SFrank Wang 4096f7b6465SFrank Wang otg_data.regs_otg = (uintptr_t)addr; 41038771996SKever Yang 41138771996SKever Yang return dwc2_udc_probe(&otg_data); 41238771996SKever Yang } 41338771996SKever Yang 41438771996SKever Yang int board_usb_cleanup(int index, enum usb_init_type init) 41538771996SKever Yang { 41638771996SKever Yang return 0; 41738771996SKever Yang } 41838771996SKever Yang #endif 419