/*	$NetBSD: odroid_machdep.c,v 1.24.4.1 2015/01/04 11:19:00 martin Exp $ */

/*
 * Copyright (c) 2014 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Reinoud Zandijk.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: odroid_machdep.c,v 1.24.4.1 2015/01/04 11:19:00 martin Exp $");

#include "opt_evbarm_boardtype.h"
#include "opt_exynos.h"
#include "opt_machdep.h"
#include "opt_ddb.h"
#include "opt_kgdb.h"
#include "opt_ipkdb.h"
#include "opt_md.h"
#include "opt_sscom.h"
#include "opt_arm_debug.h"

#include "ukbd.h"
#include "arml2cc.h"	// RPZ why is it not called opt_l2cc.h?

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/cpu.h>
#include <sys/device.h>
#include <sys/exec.h>
#include <sys/kernel.h>
#include <sys/ksyms.h>
#include <sys/msgbuf.h>
#include <sys/proc.h>
#include <sys/reboot.h>
#include <sys/termios.h>
#include <sys/gpio.h>

#include <uvm/uvm_extern.h>

#include <sys/conf.h>
#include <dev/cons.h>
#include <dev/md.h>

#include <machine/db_machdep.h>
#include <ddb/db_sym.h>
#include <ddb/db_extern.h>
#ifdef KGDB
#include <sys/kgdb.h>
#endif

#include <machine/bootconfig.h>
#include <arm/armreg.h>
#include <arm/undefined.h>
#include <arm/cortex/pl310_var.h>

#include <arm/arm32/machdep.h>
#include <arm/mainbus/mainbus.h>

#include <arm/samsung/exynos4_reg.h>
#include <arm/samsung/exynos5_reg.h>
#include <arm/samsung/exynos_var.h>

#include <evbarm/include/autoconf.h>
#include <evbarm/odroid/platform.h>

#include <dev/i2c/i2cvar.h>
#include <dev/i2c/ddcreg.h>

#include <dev/usb/ukbdvar.h>
#include <net/if_ether.h>


/* sanity checks */
#ifndef EXYNOS4
#ifndef EXYNOS5
#error Please define _either_ an EXYNOS4 or an EXYNOS5 cpu, not mixed
#endif
#endif


/* serial console stuff */
#include "sscom.h"
#include "opt_sscom.h"

#if defined(KGDB) || \
    defined(SSCOM0CONSOLE) || defined(SSCOM1CONSOLE) || \
    defined(SSCOM2CONSOLE) || defined(SSCOM3CONSOLE)
#if NSSCOM == 0
#error must define sscom for use with serial console or KGD
#endif

#include <arm/samsung/sscom_var.h>
#include <arm/samsung/sscom_reg.h>

extern const int num_exynos_uarts_entries;
extern const struct sscom_uart_info exynos_uarts[];

/* sanity checks for serial console */
#ifndef CONSPEED
#define CONSPEED 115200
#endif	/* CONSPEED */
#ifndef CONMODE
#define CONMODE ((TTYDEF_CFLAG & ~(CSIZE | CSTOPB | PARENB | HUPCL)) | CS8) /* 8N1 */
#endif	/* CONMODE */

// __CTASSERT(EXYNOS_CORE_PBASE + EXYNOS_UART0_OFFSET <= CONADDR);
// __CTASSERT(CONADDR <= EXYNOS_CORE_PBASE + EXYNOS_UART4_OFFSET);
// __CTASSERT(CONADDR % EXYNOS_BLOCK_SIZE == 0);
//static const bus_addr_t conaddr = CONADDR;
static const int conspeed = CONSPEED;
static const int conmode = CONMODE;
#endif /*defined(KGDB) || defined(SSCOM*CONSOLE) */

/*
 * uboot passes 4 arguments to us.
 *
 * arg0 arg1 arg2 arg3 : the `bootargs' environment variable from the uboot
 * context (in PA!)
 *
 * Note that the storage has to be in .data and not in .bss. On kernel start
 * the .bss is cleared and this information would get lost.
 */
uintptr_t uboot_args[4] = { 0 };


/*
 * argument and boot configure storage
 */
BootConfig bootconfig;				/* for pmap's sake */
char bootargs[MAX_BOOT_STRING] = "";		/* copied string from uboot */
char *boot_args = NULL;				/* MI bootargs */
char *boot_file = NULL;				/* MI bootfile */
uint8_t uboot_enaddr[ETHER_ADDR_LEN] = {};


/*
 * kernel start and end from the linker
 */
extern char KERNEL_BASE_phys[];	/* physical start of kernel */
extern char _end[];		/* physical end of kernel */
#define KERNEL_BASE_PHYS	((paddr_t)KERNEL_BASE_phys)

#define EXYNOS_IOPHYSTOVIRT(a) \
    ((vaddr_t)(((a) - EXYNOS_CORE_PBASE) + EXYNOS_CORE_VBASE))

/* XXX we have no framebuffer implementation yet so com is console XXX */
int use_fb_console = false;


/* prototypes */
void consinit(void);
#ifdef KGDB
static void kgdb_port_init(void);
#endif
static void exynos_usb_powercycle_lan9730(device_t self);
static void exynos_extract_mac_adress(void);
void odroid_device_register(device_t self, void *aux);
void odroid_device_register_post_config(device_t self, void *aux);


/*
 * Our static device mappings at fixed virtual addresses so we can use them
 * while booting the kernel.
 *
 * Map the extents segment-aligned and segment-rounded in size to avoid L2
 * page tables
 */

#define _A(a)	((a) & ~L1_S_OFFSET)
#define _S(s)	(((s) + L1_S_SIZE - 1) & (~(L1_S_SIZE-1)))

static const struct pmap_devmap e4_devmap[] = {
	{
		/* map in core IO space */
		.pd_va    = _A(EXYNOS_CORE_VBASE),
		.pd_pa    = _A(EXYNOS_CORE_PBASE),
		.pd_size  = _S(EXYNOS4_CORE_SIZE),
		.pd_prot  = VM_PROT_READ | VM_PROT_WRITE,
		.pd_cache = PTE_NOCACHE
	},
	{
		/* map in audiocore IO space */
		.pd_va    = _A(EXYNOS4_AUDIOCORE_VBASE),
		.pd_pa    = _A(EXYNOS4_AUDIOCORE_PBASE),
		.pd_size  = _S(EXYNOS4_AUDIOCORE_SIZE),
		.pd_prot  = VM_PROT_READ | VM_PROT_WRITE,
		.pd_cache = PTE_NOCACHE
	},
	{0}
};

static const struct pmap_devmap e5_devmap[] = {
	{
		/* map in core IO space */
		.pd_va    = _A(EXYNOS_CORE_VBASE),
		.pd_pa    = _A(EXYNOS_CORE_PBASE),
		.pd_size  = _S(EXYNOS5_CORE_SIZE),
		.pd_prot  = VM_PROT_READ | VM_PROT_WRITE,
		.pd_cache = PTE_NOCACHE
	},
	{
		/* map in audiocore IO space */
		.pd_va    = _A(EXYNOS5_AUDIOCORE_VBASE),
		.pd_pa    = _A(EXYNOS5_AUDIOCORE_PBASE),
		.pd_size  = _S(EXYNOS5_AUDIOCORE_SIZE),
		.pd_prot  = VM_PROT_READ | VM_PROT_WRITE,
		.pd_cache = PTE_NOCACHE
	},
	{0}
};
#undef _A
#undef _S

#ifdef PMAP_NEED_ALLOC_POOLPAGE
static struct boot_physmem bp_highgig = {
	.bp_pages = (KERNEL_VM_BASE - KERNEL_BASE) / NBPG,
	.bp_freelist = VM_FREELIST_ISADMA,
	.bp_flags = 0,
};
#endif

/*
 * u_int initarm(...)
 *
 * Our entry point from the assembly before main() is called.
 * - take a copy of the config we got from uboot
 * - init the physical console
 * - setting up page tables for the kernel
 */

u_int
initarm(void *arg)
{
	const struct pmap_devmap const *devmap;
	bus_addr_t rambase;
	const psize_t ram_reserve = 0x200000;
	psize_t ram_size;

	/* allocate/map our basic memory mapping */
    	switch (EXYNOS_PRODUCT_FAMILY(exynos_soc_id)) {
#if defined(EXYNOS4)
	case EXYNOS4_PRODUCT_FAMILY:
		devmap = e4_devmap;
		rambase = EXYNOS4_SDRAM_PBASE;
		break;
#endif
#if defined(EXYNOS5)
	case EXYNOS5_PRODUCT_FAMILY:
		devmap = e5_devmap;
		rambase = EXYNOS5_SDRAM_PBASE;
		break;
#endif
	default:
		/* Won't work, but... */
		panic("Unknown product family %llx",
		   EXYNOS_PRODUCT_FAMILY(exynos_soc_id));
	}
	pmap_devmap_register(devmap);

	/* bootstrap soc. uart_address is determined in odroid_start */
	paddr_t uart_address = armreg_tpidruro_read();
	exynos_bootstrap(EXYNOS_CORE_VBASE, EXYNOS_IOPHYSTOVIRT(uart_address));

	/* set up CPU / MMU / TLB functions */
	if (set_cpufuncs())
		panic("cpu not recognized!");

	/* get normal console working */
 	consinit();

#ifdef KGDB
	kgdb_port_init();
#endif

#ifdef VERBOSE_INIT_ARM
	printf("\nuboot arg = %#"PRIxPTR", %#"PRIxPTR", %#"PRIxPTR", %#"PRIxPTR"\n",
	    uboot_args[0], uboot_args[1], uboot_args[2], uboot_args[3]);
	printf("Exynos SoC ID %08x\n", exynos_soc_id);

	printf("initarm: cbar=%#x\n", armreg_cbar_read());
#endif

	/* determine cpu0 clock rate */
	exynos_clocks_bootstrap();
#ifdef VERBOSE_INIT_ARM
	printf("CPU0 now running on %"PRIu64" Mhz\n", exynos_get_cpufreq()/(1000*1000));
#endif

#if NARML2CC > 0
	if (CPU_ID_CORTEX_A9_P(curcpu()->ci_arm_cpuid)) {
		/* probe and enable the PL310 L2CC */
		const bus_space_handle_t pl310_bh =
			EXYNOS_IOPHYSTOVIRT(armreg_cbar_read());

#ifdef ARM_TRUSTZONE_FIRMWARE
		exynos4_l2cc_init();
#endif
		arml2cc_init(&exynos_bs_tag, pl310_bh, 0x2000);
	}
#endif

	cpu_reset_address = exynos_wdt_reset;

#ifdef VERBOSE_INIT_ARM
	printf("\nNetBSD/evbarm (odroid) booting ...\n");
#endif

#ifdef BOOT_ARGS
	char mi_bootargs[] = BOOT_ARGS;
	parse_mi_bootargs(mi_bootargs);
#endif
	boot_args = bootargs;
	parse_mi_bootargs(boot_args);
	exynos_extract_mac_adress();

	/*
	 * Determine physical memory by looking at the PoP package. This PoP
	 * package ID seems to be only available on Exynos4
	 *
	 * First assume the default 2Gb of memory, dictated by mapping too
	 */
	ram_size = (psize_t) 0xC0000000 - 0x40000000;

#if defined(EXYNOS4)
	switch (exynos_pop_id) {
	case EXYNOS_PACKAGE_ID_2_GIG:
		KASSERT(ram_size <= 2UL*1024*1024*1024);
		break;
	default:
		printf("Unknown PoP package id 0x%08x, assuming 1Gb\n",
			exynos_pop_id);
		ram_size = (psize_t) 0x10000000;
	}
#endif

	/* Fake bootconfig structure for the benefit of pmap.c. */
	bootconfig.dramblocks = 1;
	bootconfig.dram[0].address = rambase;
	bootconfig.dram[0].pages = ram_size / PAGE_SIZE;

#ifdef __HAVE_MM_MD_DIRECT_MAPPED_PHYS
	const bool mapallmem_p = true;
#ifndef PMAP_NEED_ALLOC_POOLPAGE
	if (ram_size > KERNEL_VM_BASE - KERNEL_BASE) {
		printf("%s: dropping RAM size from %luMB to %uMB\n",
		   __func__, (unsigned long) (ram_size >> 20),
		   (KERNEL_VM_BASE - KERNEL_BASE) >> 20);
		ram_size = KERNEL_VM_BASE - KERNEL_BASE;
	}
#endif
#else
	const bool mapallmem_p = false;
#endif
	KASSERT((armreg_pfr1_read() & ARM_PFR1_SEC_MASK) != 0);

	arm32_bootmem_init(bootconfig.dram[0].address, ram_size - ram_reserve,
	    KERNEL_BASE_PHYS);
	arm32_kernel_vm_init(KERNEL_VM_BASE, ARM_VECTORS_LOW, 0, devmap,
	    mapallmem_p);

	/* we've a specific device_register routine */
	evbarm_device_register = odroid_device_register;
	evbarm_device_register_post_config = odroid_device_register_post_config;

	/*
	 * If we couldn't map all of memory via TTBR1, limit the memory the
	 * kernel can allocate from to be from the highest available 1GB.
	 */
#ifdef PMAP_NEED_ALLOC_POOLPAGE
	if (atop(ram_size) > bp_highgig.bp_pages) {
		bp_highgig.bp_start = rambase / NBPG
		     + atop(ram_size) - bp_highgig.bp_pages;
		bp_highgig.bp_pages -= atop(ram_reserve); 
		arm_poolpage_vmfreelist = bp_highgig.bp_freelist;
		return initarm_common(KERNEL_VM_BASE, KERNEL_VM_SIZE,
		    &bp_highgig, 1);
	}
#endif

	return initarm_common(KERNEL_VM_BASE, KERNEL_VM_SIZE, NULL, 0);
}


void
consinit(void)
{
	static bool consinit_called;

	if (consinit_called)
		return;
	consinit_called = true;

#if NSSCOM > 0
	bus_space_tag_t bst = &exynos_bs_tag;
	bus_addr_t iobase = armreg_tpidruro_read();
	bus_space_handle_t bsh = EXYNOS_IOPHYSTOVIRT(iobase);
	u_int i;
	/*	
	 * No need to guess at the UART frequency since we can calculate it.
	 */
	uint32_t freq = conspeed
	   * (16 * (bus_space_read_4(bst, bsh, SSCOM_UBRDIV) + 1)
		 + bus_space_read_4(bst, bsh, SSCOM_UFRACVAL));
	freq = (freq + conspeed / 2) / 1000;
	freq *= 1000;

	/* go trough all entries */
	for (i = 0; i < num_exynos_uarts_entries; i++) {
		/* attach console */
		if (exynos_uarts[i].iobase + EXYNOS_CORE_PBASE == iobase)
			break;
	}
	KASSERT(i < num_exynos_uarts_entries);
	printf("%s: attaching console @ %#"PRIxPTR" (%u HZ, %u bps)\n",
	    __func__, iobase, freq, conspeed);
	if (sscom_cnattach(bst, exynos_core_bsh, &exynos_uarts[i],
			   conspeed, freq, conmode))
		panic("Serial console can not be initialized");
#ifdef VERBOSE_INIT_ARM
	printf("Console initialized\n");
#endif
#else
#error only serial console is supported
#if NUKBD > 0
	/* allow USB keyboards to become console */
	ukbd_cnattach();
#endif /* NUKBD */
#endif
}


/* extract ethernet mac address from bootargs */
static void
exynos_extract_mac_adress(void)
{
	char *str, *ptr;
	int i, v1, v2, val;

#define EXPECT_COLON() {\
		v1 = *ptr++; \
		if (v1 != ':') break; \
	}
#define EXPECT_HEX(v) {\
		(v) = (v) >= '0' && (v) <= '9'? (v) - '0' : \
		      (v) >= 'a' && (v) <= 'f'? (v) - 'a' + 10 : \
		      (v) >= 'A' && (v) <= 'F'? (v) - 'A' + 10 : -1; \
		if ((v) < 0) break; \
	}
#define EXPECT_2HEX(val) {\
		v1 = *ptr++; EXPECT_HEX(v1); \
		v2 = *ptr++; EXPECT_HEX(v2); \
		val = (v1 << 4) | v2; \
	}
	if (get_bootconf_option(boot_args, "ethaddr",
			BOOTOPT_TYPE_STRING, &str)) {
		for (i = 0, ptr = str; i < sizeof(uboot_enaddr); i++) {
			if (i)
				EXPECT_COLON();
			EXPECT_2HEX(val);
			uboot_enaddr[i] = val;
		}
		if (i != sizeof(uboot_enaddr)) {
			printf( "Ignoring invalid MAC address '%s' passed "
				"as boot paramter `ethaddr'\n", str);
			memset((char *) uboot_enaddr, 0, sizeof(uboot_enaddr));
		}
	}
#undef EXPECT_2HEX
#undef EXPECT_HEX
#undef EXPECT_COLON
}


#ifdef EXYNOS4
static void
odroid_exynos4_gpio_ncs(device_t self, prop_dictionary_t dict) {
	/*
	 * These pins have no connections; the 1st is a raw values that is
	 * generated by the gpio bootstrap and the values substracted are
	 * explicitly allowed
	 */
	/* i2c at pin 6,7 */
	prop_dictionary_set_uint32(dict, "nc-GPA0", 0xff - 0b11000000);
	/* p3v3 enable on pin 3 */
	prop_dictionary_set_uint32(dict, "nc-GPA1", 0x3f - 0b00001000);
	prop_dictionary_set_uint32(dict, "nc-GPB",  0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPC0", 0x1f - 0b00000000);
	/* blue led at bit 0 : heartbeat */
	prop_dictionary_set_uint32(dict, "nc-GPC1", 0x1f - 0b00000001);
	prop_dictionary_set_uint32(dict, "nc-GPD0", 0x0f - 0b00000000);
	/* i2c0 at pin 0,1 and i2c1 at pin 2,3 : */
	prop_dictionary_set_uint32(dict, "nc-GPD1", 0x0f - 0b00001111);
	prop_dictionary_set_uint32(dict, "nc-GPF0", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPF1", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPF2", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPF3", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPJ0", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPJ1", 0x1f - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPK0", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPK1", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPK2", 0x7f - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPK3", 0x7f - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPL0", 0x7f - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPL1", 0x03 - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPL2", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPY0", 0x3f - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPY1", 0x0f - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPY2", 0x3f - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPY3", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPY4", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPY5", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPY6", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-ETC0", 0x3f - 0b00000000);
	/* standard Xuhost bits at pin 6,7 */
	prop_dictionary_set_uint32(dict, "nc-ETC6", 0xff - 0b11000000);
	prop_dictionary_set_uint32(dict, "nc-GPM0", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPM1", 0x7f - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPM2", 0x1f - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPM3", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPM4", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPX0", 0xff - 0b00000000);
	/* expansion connector bits at pin 0,1,5 : */
	prop_dictionary_set_uint32(dict, "nc-GPX1", 0xff - 0b00100011);
	prop_dictionary_set_uint32(dict, "nc-GPX2", 0xff - 0b00000000);
	/* usb hub communication at pin 0,4,5 : */
	prop_dictionary_set_uint32(dict, "nc-GPX3", 0xff - 0b00110001);
	prop_dictionary_set_uint32(dict, "nc-GPZ",  0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPV0", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPV1", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-ETC7", 0x03 - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPV2", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPV3", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-ETC8", 0x03 - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPV4", 0x03 - 0b00000000);
}
#endif


#ifdef EXYNOS5
static void
odroid_exynos5_gpio_ncs(device_t self, prop_dictionary_t dict) {
	/*
	 * These pins have no connections; the 1st is a raw values that is
	 * generated by the gpio bootstrap and the values substracted are
	 * explicitly allowed
	 */
	/* i2c2 at pin 6,7 */
	prop_dictionary_set_uint32(dict, "nc-GPA0", 0xff - 0b11000000);
	prop_dictionary_set_uint32(dict, "nc-GPA1", 0x3f - 0b00000000);
	/* i2c4 at pin 0,1 */
	prop_dictionary_set_uint32(dict, "nc-GPA2", 0xff - 0b00000011);
	prop_dictionary_set_uint32(dict, "nc-GPB0", 0x1f - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPB1", 0x1f - 0b00000000);
	/* green led at bit 1 : eMMC activity */
	/* red   led at bit 2 : heartbeat */
	prop_dictionary_set_uint32(dict, "nc-GPB2", 0x0f - 0b00000110);
	/* i2c1 at pin 2,3 */
	prop_dictionary_set_uint32(dict, "nc-GPB3", 0x0f - 0b00001100);
	prop_dictionary_set_uint32(dict, "nc-GPC0", 0x7f - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPC1", 0x0f - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPC2", 0x7f - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPC3", 0x7f - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPD0", 0x0f - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPD1", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPY0", 0x3f - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPY1", 0x0f - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPY2", 0x3f - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPY3", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPY4", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPY5", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPY6", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-ETC0", 0x3f - 0b00000000);
	/* standard Xuhost bits at pin 5,6 */
	prop_dictionary_set_uint32(dict, "nc-ETC6", 0x7f - 0b01100000);
	prop_dictionary_set_uint32(dict, "nc-ETC7", 0x1f - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPC4", 0x3f - 0b00000000);
	/* usb hub communication at bit 6,7 : */
	prop_dictionary_set_uint32(dict, "nc-GPX0", 0xff - 0b11000000);
	/* usb hub communication at bit 4 : */
	prop_dictionary_set_uint32(dict, "nc-GPX1", 0xff - 0b00010000);
	/* blue led at bit 3 : microSD activity */
	prop_dictionary_set_uint32(dict, "nc-GPX2", 0xff - 0b00001000);
	prop_dictionary_set_uint32(dict, "nc-GPX3", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPE0", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPE1", 0x03 - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPF0", 0x0f - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPF1", 0x0f - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPG0", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPG1", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPG2", 0x03 - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPH0", 0x0f - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPH1", 0xff - 0b00000000);

	prop_dictionary_set_uint32(dict, "nc-GPJ0", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPJ1", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPJ2", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPJ3", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPJ4", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPK0", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPK1", 0xff - 0b00000000);
	/* usb3 overcur1{2,3} at bits 4,5, vbus1 at pin 7 */
	prop_dictionary_set_uint32(dict, "nc-GPK2", 0xff - 0b10110000);
	/* usb3 overcur0{2,3} at bits 0,1, vbus0 at pin 3 */
	prop_dictionary_set_uint32(dict, "nc-GPK3", 0xff - 0b00001011);

	prop_dictionary_set_uint32(dict, "nc-GPV0", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPV1", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-ETC5", 0x03 - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPV2", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPV3", 0xff - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-ETC8", 0x03 - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPV4", 0x03 - 0b00000000);
	prop_dictionary_set_uint32(dict, "nc-GPZ",  0x7f - 0b00000000);
}
#endif


void
odroid_device_register(device_t self, void *aux)
{
	prop_dictionary_t dict = device_properties(self);

	exynos_device_register(self, aux);

	if (device_is_a(self, "sscom")) {
		if (use_fb_console)
			prop_dictionary_set_bool(dict, "is_console", false);
	}

	if (device_is_a(self, "mct"))
		prop_dictionary_set_cstring(dict, "heartbeat", "led1");

	if (device_is_a(self, "exyousb")) {
		prop_dictionary_set_cstring(dict, "nreset", "hub_nreset");
		prop_dictionary_set_cstring(dict, "hubconnect", "hub_connect");
		prop_dictionary_set_cstring(dict, "nint", "hub_nint");
		prop_dictionary_set_cstring(dict, "lan_power", "p3v3_en");
	}

	if (device_is_a(self, "usmsc")) {
		prop_data_t blob =
		    prop_data_create_data(uboot_enaddr, ETHER_ADDR_LEN);
		if (prop_dictionary_set(dict, "mac-address", blob) == false) {
			aprint_error_dev(self,
			    "WARNING: unable to set mac-address property\n");
		}
		prop_object_release(blob);
	}

#ifdef EXYNOS4
	if (device_is_a(self, "exyogpio")) {
		/* unused bits */
		odroid_exynos4_gpio_ncs(self, dict);

		/* explicit pin settings */
		prop_dictionary_set_cstring(dict, "led1", ">GPC1[0]");

		prop_dictionary_set_cstring(dict, "hub_nreset", ">GPX3[5]");
		prop_dictionary_set_cstring(dict, "hub_connect", ">GPX3[4]");
		prop_dictionary_set_cstring(dict, "hub_nint", "<GPX3[0]");

		prop_dictionary_set_cstring(dict, "p3v3_en", ">GPA1[3]");
	}
	if (device_is_a(self, "exyoiic")) {
		prop_dictionary_set_bool(dict, "iic0_enable", true);
		prop_dictionary_set_bool(dict, "iic1_enable", true);
		prop_dictionary_set_bool(dict, "iic2_enable", true);
		/* IIC3 not used (NC) */
		/* IIC4 not used (NC) */
		/* IIC5 not used (NC) */
		/* IIC6 used differently (SCLK used as led1) */
		/* IIC7 used differently (PWM, though NC)    */
		/* IIC8 HDMI, not possible trough GPIO */
	}
#endif
#ifdef EXYNOS5
	if (device_is_a(self, "exyogpio")) {
		/* unused bits */
		odroid_exynos5_gpio_ncs(self, dict);

		/* explicit pin settings */
		prop_dictionary_set_cstring(dict, "hub_nreset", ">GPX1[4]");
		prop_dictionary_set_cstring(dict, "hub_connect", ">GPX0[6]");
		prop_dictionary_set_cstring(dict, "hub_nint", "<GPX0[7]");

		/* internal hub IIRC, unknown if this line exists */
		//prop_dictionary_set_cstring(dict, "p3v3_en", ">GPA1[3]");
	}
	if (device_is_a(self, "exyoiic")) {
		/* IIC0 not used (NC) */
		prop_dictionary_set_bool(dict, "iic1_enable", true);
		prop_dictionary_set_bool(dict, "iic2_enable", true);
		/* IIC3 not used (NC) */
		prop_dictionary_set_bool(dict, "iic4_enable", true);
		/* IIC5 not used (NC) */
		/* IIC6 used differently (SCLK used as led1) */
		/* IIC7 used differently (PWM, though NC)    */
		/* IIC8 HDMI, not possible trough GPIO */
	}
#endif
}


static void
exynos_usb_init_usb3503_hub(device_t self)
{
	prop_dictionary_t dict = device_properties(self);
	const char *pin_nreset, *pin_hubconnect, *pin_nint;
	struct exynos_gpio_pindata nreset_pin, hubconnect_pin, nint_pin;
	int ok1, ok2, ok3;

	prop_dictionary_get_cstring_nocopy(dict, "nreset", &pin_nreset);
	prop_dictionary_get_cstring_nocopy(dict, "hubconnect", &pin_hubconnect);
	prop_dictionary_get_cstring_nocopy(dict, "nint", &pin_nint);
	if (!(pin_nreset && pin_hubconnect && pin_nint)) {
		aprint_error_dev(self,
			"failed to lookup GPIO pins for usb3503 hub init");
		return;
	}

	ok1 = exynos_gpio_pin_reserve(pin_nreset, &nreset_pin);
	ok2 = exynos_gpio_pin_reserve(pin_hubconnect, &hubconnect_pin);
	ok3 = exynos_gpio_pin_reserve(pin_nint, &nint_pin);
	if (!ok1)
		aprint_error_dev(self,
		    "can't reserve GPIO pin %s\n", pin_nreset);
	if (!ok2)
		aprint_error_dev(self,
		    "can't reserve GPIO pin %s\n", pin_hubconnect);
	if (!ok3)
		aprint_error_dev(self,
		    "can't reserve GPIO pin %s\n", pin_nint);
	if (!(ok1 && ok2 && ok3))
		return;

	/* reset pin to zero */
	exynos_gpio_pindata_write(&nreset_pin, 0);
	DELAY(10000);

	/* pull intn low */
	exynos_gpio_pindata_ctl(&nint_pin, GPIO_PIN_PULLDOWN);

	/* set hubconnect low */
	exynos_gpio_pindata_write(&hubconnect_pin, 0);

	/* reset pin up again, hub enters RefClk stage */
	exynos_gpio_pindata_write(&nreset_pin, 1);
	DELAY(10000);

	/* release intn */
	exynos_gpio_pindata_ctl(&nint_pin, GPIO_PIN_TRISTATE);

	/* set hubconnect high */
	exynos_gpio_pindata_write(&hubconnect_pin, 1);
	DELAY(40000);

	/* DONE! */
}


#if 0
static void
exynos_max77686_dump(struct i2c_controller *i2c)
{
	int error;

	printf("%s:\n", __func__);
	for (int i = 0; i < 0x80; i++) {
		error = iic_exec(i2c, I2C_OP_READ_WITH_STOP, 0x09, &i, 1,
				&rdata, sizeof(rdata), 0);
		KASSERT(!error);
		if (i % 16 == 0)
			printf("\n%02x: ", i);
		printf("%02x ", rdata);
	}
	printf("\n");
}
#endif


static void
exynos_usb_powercycle_lan9730(device_t self)
{
#ifdef EXYNOS4
	prop_dictionary_t dict = device_properties(self);
	struct exynos_gpio_pindata enable_pin;
	struct i2c_controller *i2c;
	const char *pin_enable;
	uint8_t rdata, wdata, reg;
	int error __diagused;
	bool ok;

	/*
	 * XXX fixed for now:
	 *    Odroid-U2/U3 : Max77686 chip is attached to iic0 chipid 0x09
	 */
	const int iicbus      = 0;
	const int chipid      = 0x09;
	const int buck_outreg = 0x37;	/* buck 8 output voltage */
	const int buck_ctlreg = 0x38;	/* buck 8 power control */

	i2c = exynos_i2cbus[iicbus];
	KASSERT(i2c);
	iic_acquire_bus(i2c, 0);

	/* set power level to 0v */
	reg = buck_outreg;
	wdata = 0x0;
	error = iic_exec(i2c, I2C_OP_WRITE_WITH_STOP, chipid, &reg, 1,
			&wdata, sizeof(wdata), 0);
	KASSERT(!error);
	DELAY(20000);

	/* set power level back to 3.3v */
	wdata = 0x33;
	error = iic_exec(i2c, I2C_OP_WRITE_WITH_STOP, chipid, &reg, 1,
			&wdata, sizeof(wdata), 0);
	KASSERT(!error);
	DELAY(20000);

	/* enable the bucket explicitly */
	reg = buck_ctlreg;
        error = iic_exec(i2c, I2C_OP_READ_WITH_STOP, chipid, &reg, 1,
			&rdata, sizeof(rdata), 0);
	KASSERT(!error);
	rdata |= 3;
	error = iic_exec(i2c, I2C_OP_WRITE_WITH_STOP, chipid, &reg, 1,
			&rdata, sizeof(rdata), 0);
	KASSERT(!error);
	DELAY(30000);

	iic_release_bus(i2c, 0);

	/* enable lan chip power */
	prop_dictionary_get_cstring_nocopy(dict, "lan_power", &pin_enable);
	if (pin_enable)  {
		ok = exynos_gpio_pin_reserve(pin_enable, &enable_pin);
		if (!ok) {
			aprint_error_dev(self,
				"can't reserve GPIO pin %s\n", pin_enable);
		} else {
			exynos_gpio_pindata_write(&enable_pin, 0);
			DELAY(30000);
			exynos_gpio_pindata_write(&enable_pin, 1);
			DELAY(30000);
		}
	} else {
		aprint_error_dev(self, "failed to lookup lan_power GPIO pin");
	}
#endif
}


void
odroid_device_register_post_config(device_t self, void *aux)
{
	exynos_device_register_post_config(self, aux);

	if (device_is_a(self, "exyousb")) {
		exynos_usb_powercycle_lan9730(self);
		exynos_usb_init_usb3503_hub(self);
	}
}


/*
 * Odroid specific tweaks
 */
/*
 * The external USB devices are clocked trough the DEBUG clkout
 * XXX is this Odroid specific? XXX
 */
void
exynos_init_clkout_for_usb(void)
{
#ifdef EXYNOS4
	/* Select XUSBXTI as source for CLKOUT */
	bus_space_write_4(&exynos_bs_tag, exynos_pmu_bsh,
		EXYNOS_PMU_DEBUG_CLKOUT, 0x900);
#endif
#ifdef EXYNOS5
	/* Select XUSBXTI as source for CLKOUT */
	bus_space_write_4(&exynos_bs_tag, exynos_pmu_bsh,
		EXYNOS_PMU_DEBUG_CLKOUT, 0x1000);
#endif
}

