/* $NetBSD: amlogic_board.c,v 1.16 2015/11/26 00:06:59 jmcneill Exp $ */

/*-
 * Copyright (c) 2015 Jared D. McNeill <jmcneill@invisible.ca>
 * All rights reserved.
 *
 * 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 AUTHOR ``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 AUTHOR 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 "opt_amlogic.h"

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: amlogic_board.c,v 1.16 2015/11/26 00:06:59 jmcneill Exp $");

#define	_ARM32_BUS_DMA_PRIVATE
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/cpu.h>
#include <sys/device.h>
#include <sys/wdog.h>

#include <dev/sysmon/sysmonvar.h>

#include <uvm/uvm_extern.h>

#include <arm/bootconfig.h>
#include <arm/cpufunc.h>

#include <arm/amlogic/amlogic_reg.h>
#include <arm/amlogic/amlogic_crureg.h>
#include <arm/amlogic/amlogic_var.h>

#define AMLOGIC_EE_WDOG_PERIOD_DEFAULT	8
#define AMLOGIC_EE_WDOG_PERIOD_MAX	8
#define AMLOGIC_EE_WDOG_TICKS_PER_SEC	7812

bus_space_handle_t amlogic_core_bsh;

static int	amlogic_ee_wdog_setmode(struct sysmon_wdog *);
static int	amlogic_ee_wdog_tickle(struct sysmon_wdog *);

static struct sysmon_wdog amlogic_ee_wdog = {
	.smw_name = "EE-watchdog",
	.smw_setmode = amlogic_ee_wdog_setmode,
	.smw_tickle = amlogic_ee_wdog_tickle,
	.smw_period = AMLOGIC_EE_WDOG_PERIOD_DEFAULT
};

struct arm32_bus_dma_tag amlogic_dma_tag = {
	_BUS_DMAMAP_FUNCS,
	_BUS_DMAMEM_FUNCS,
	_BUS_DMATAG_FUNCS,
};

#define CBUS_READ(x)		\
	bus_space_read_4(&armv7_generic_bs_tag, amlogic_core_bsh, \
			 AMLOGIC_CBUS_OFFSET + (x))
#define CBUS_WRITE(x, v)	\
	bus_space_write_4(&armv7_generic_bs_tag, amlogic_core_bsh, \
			  AMLOGIC_CBUS_OFFSET + (x), (v))

#define CBUS_SET_CLEAR(x, s, c)	\
	amlogic_reg_set_clear(&armv7_generic_bs_tag, amlogic_core_bsh, \
			      AMLOGIC_CBUS_OFFSET + (x), (s), (c))

void
amlogic_bootstrap(void)
{
	int error;

	error = bus_space_map(&armv7_generic_bs_tag, AMLOGIC_CORE_BASE,
	    AMLOGIC_CORE_SIZE, 0, &amlogic_core_bsh);
	if (error)
		panic("%s: failed to map CORE registers: %d", __func__, error);

	curcpu()->ci_data.cpu_cc_freq = amlogic_get_rate_a9();
}

uint32_t
amlogic_get_rate_xtal(void)
{
	uint32_t ctlreg0;
	
	ctlreg0 = CBUS_READ(PREG_CTLREG0_ADDR_REG);

	return __SHIFTOUT(ctlreg0, PREG_CTLREG0_ADDR_CLKRATE) * 1000000;
}

uint32_t
amlogic_get_rate_sys(void)
{
	uint32_t cntl;
	uint64_t clk;
	u_int mul, div, od;

	clk = amlogic_get_rate_xtal();
	cntl = CBUS_READ(HHI_SYS_PLL_CNTL_REG);
	mul = __SHIFTOUT(cntl, HHI_SYS_PLL_CNTL_MUL);
	div = __SHIFTOUT(cntl, HHI_SYS_PLL_CNTL_DIV);
	od = __SHIFTOUT(cntl, HHI_SYS_PLL_CNTL_OD);

	clk *= mul;
	clk /= div;
	clk >>= od;

	return (uint32_t)clk;
}

uint32_t
amlogic_get_rate_clk81(void)
{
	uint32_t cc, rate;

	rate = amlogic_get_rate_fixed();
	cc = CBUS_READ(HHI_MPEG_CLK_CNTL_REG);
	
	switch (__SHIFTOUT(cc, HHI_MPEG_CLK_CNTL_DIV_SRC)) {
	case 7:
		rate /= 5;
		break;
	case 6:
		rate /= 3;
		break;
	case 5:
		rate /= 4;
		break;
	default:
		panic("CLK81: unknown rate, HHI_MPEG_CLK_CNTL_REG = %#x", cc);
	}

	return rate / (__SHIFTOUT(cc, HHI_MPEG_CLK_CNTL_DIV_N) + 1);
}

uint32_t
amlogic_get_rate_fixed(void)
{
	uint32_t cntl;
	uint64_t clk;
	u_int mul, div, od;

	clk = amlogic_get_rate_xtal();
	cntl = CBUS_READ(HHI_MPLL_CNTL_REG);
	mul = __SHIFTOUT(cntl, HHI_MPLL_CNTL_MUL);
	div = __SHIFTOUT(cntl, HHI_MPLL_CNTL_DIV);
	od = __SHIFTOUT(cntl, HHI_MPLL_CNTL_OD);

	clk *= mul;
	clk /= div;
	clk >>= od;

	return (uint32_t)clk;
}

uint32_t
amlogic_get_rate_a9(void)
{
	uint32_t cntl0, cntl1;
	uint32_t rate = 0;

	cntl0 = CBUS_READ(HHI_SYS_CPU_CLK_CNTL0_REG);
	if (cntl0 & HHI_SYS_CPU_CLK_CNTL0_CLKSEL) {
		switch (__SHIFTOUT(cntl0, HHI_SYS_CPU_CLK_CNTL0_PLLSEL)) {
		case 0:
			rate = amlogic_get_rate_xtal();
			break;
		case 1:
			rate = amlogic_get_rate_sys();
			break;
		case 2:
			rate = 1250000000;
			break;
		}
	} else {
		rate = amlogic_get_rate_xtal();
	}

	KASSERTMSG(rate != 0, "couldn't determine A9 rate");

	switch (__SHIFTOUT(cntl0, HHI_SYS_CPU_CLK_CNTL0_SOUTSEL)) {
	case 0:
		break;
	case 1:
		rate /= 2;
		break;
	case 2:
		rate /= 3;
		break;
	case 3:
		cntl1 = CBUS_READ(HHI_SYS_CPU_CLK_CNTL1_REG);
		rate /= (__SHIFTOUT(cntl1, HHI_SYS_CPU_CLK_CNTL1_SDIV) + 1);
		break;
	}

	return rate;
}

uint32_t
amlogic_get_rate_a9periph(void)
{
	const uint32_t cntl1 = CBUS_READ(HHI_SYS_CPU_CLK_CNTL1_REG);
	const u_int div = __SHIFTOUT(cntl1,
				     HHI_SYS_CPU_CLK_CNTL1_PERIPH_CLK_MUX) + 2;

	return amlogic_get_rate_a9() / div;
}

void
amlogic_eth_init(void)
{
	CBUS_WRITE(EE_CLK_GATING1_REG,
	    CBUS_READ(EE_CLK_GATING1_REG) | EE_CLK_GATING1_ETHERNET);
}

void
amlogic_rng_init(void)
{
	CBUS_SET_CLEAR(EE_CLK_GATING0_REG, EE_CLK_GATING0_RNG, 0);
	CBUS_SET_CLEAR(EE_CLK_GATING3_REG, EE_CLK_GATING3_RNG, 0);
	CBUS_SET_CLEAR(AM_RING_OSC_REG,
	    AM_RING_OSC_ENABLE | AM_RING_OSC_HF_MODE, 0);
}

void
amlogic_wdog_init(void)
{
	/* Disable watchdog */
	CBUS_WRITE(WATCHDOG_RESET_REG, 0);
	CBUS_SET_CLEAR(WATCHDOG_TC_REG, 0, WATCHDOG_TC_ENABLE);

	sysmon_wdog_register(&amlogic_ee_wdog);
}

static int
amlogic_ee_wdog_setmode(struct sysmon_wdog *smw)
{
	if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) {
		CBUS_WRITE(WATCHDOG_RESET_REG, 0);
		CBUS_SET_CLEAR(WATCHDOG_TC_REG, 0, WATCHDOG_TC_ENABLE);
		return 0;
	}

	if (smw->smw_period == WDOG_PERIOD_DEFAULT) {
		amlogic_ee_wdog.smw_period = AMLOGIC_EE_WDOG_PERIOD_DEFAULT;
	} else if (smw->smw_period == 0 ||
		   smw->smw_period > AMLOGIC_EE_WDOG_PERIOD_MAX) {
		return EINVAL;
	} else {
		amlogic_ee_wdog.smw_period = smw->smw_period;
	}
	u_int tcnt = amlogic_ee_wdog.smw_period * AMLOGIC_EE_WDOG_TICKS_PER_SEC;
	CBUS_WRITE(WATCHDOG_RESET_REG, 0);
	CBUS_WRITE(WATCHDOG_TC_REG, WATCHDOG_TC_CPUS | WATCHDOG_TC_ENABLE |
	    __SHIFTIN(tcnt, WATCHDOG_TC_TCNT));

	return 0;
}

static int
amlogic_ee_wdog_tickle(struct sysmon_wdog *smw)
{
	CBUS_WRITE(WATCHDOG_RESET_REG, 0);
	return 0;
}

void
amlogic_sdhc_init(void)
{
	/* enable SDHC clk */
	CBUS_WRITE(EE_CLK_GATING0_REG,
	    CBUS_READ(EE_CLK_GATING0_REG) | EE_CLK_GATING0_SDHC);
}

int
amlogic_sdhc_select_port(int port)
{
	switch (port) {
	case AMLOGIC_SDHC_PORT_B:
		/* Set CARD 0-5 to input */
		CBUS_SET_CLEAR(PAD_PULL_UP_2_REG, 0x03f00000, 0);
		CBUS_SET_CLEAR(PAD_PULL_UP_EN_2_REG, 0x03f00000, 0);
		CBUS_SET_CLEAR(PREG_PAD_GPIO0_EN_N_REG, 0x0fc00000, 0);

		/* CARD -> SDHC pin mux settings */
		CBUS_SET_CLEAR(PERIPHS_PIN_MUX_5_REG, 0, 0x00007c00);
		CBUS_SET_CLEAR(PERIPHS_PIN_MUX_4_REG, 0, 0x7c000000);
		CBUS_SET_CLEAR(PERIPHS_PIN_MUX_2_REG, 0, 0x0000fc00);
		CBUS_SET_CLEAR(PERIPHS_PIN_MUX_8_REG, 0, 0x00000600);
		CBUS_SET_CLEAR(PERIPHS_PIN_MUX_2_REG, 0x000000f0, 0);

		/* XXX ODROID-C1 */
		CBUS_SET_CLEAR(PREG_PAD_GPIO5_EN_N_REG, 0x20000000, 0);
		CBUS_SET_CLEAR(PREG_PAD_GPIO5_OUT_REG, 0, 0x80000000);
		CBUS_SET_CLEAR(PREG_PAD_GPIO5_EN_N_REG, 0, 0x80000000);
		break;
	case AMLOGIC_SDHC_PORT_C:
		/* Set BOOT 0-8,10 to input */
		CBUS_SET_CLEAR(PAD_PULL_UP_2_REG, 0x000005ff, 0);
		CBUS_SET_CLEAR(PAD_PULL_UP_EN_2_REG, 0x000005ff, 0);
		CBUS_SET_CLEAR(PREG_PAD_GPIO3_EN_N_REG, 0x000005ff, 0);

		/* BOOT -> SDHC pin mux settings */
		CBUS_SET_CLEAR(PERIPHS_PIN_MUX_2_REG, 0, 0x04c000f0);
		CBUS_SET_CLEAR(PERIPHS_PIN_MUX_5_REG, 0, 0x00007c00);
		CBUS_SET_CLEAR(PERIPHS_PIN_MUX_6_REG, 0, 0xff000000);
		CBUS_SET_CLEAR(PERIPHS_PIN_MUX_4_REG, 0x70000000, 0);
		CBUS_SET_CLEAR(PERIPHS_PIN_MUX_7_REG, 0x000c0000, 0);

		/* XXX ODROID-C1 */
		CBUS_SET_CLEAR(PAD_PULL_UP_3_REG, 0, 0x000000ff);
		break;
	default:
		return EINVAL;
	}

	return 0;
}

void
amlogic_sdhc_reset_port(int port)
{
	switch (port) {
	case AMLOGIC_SDHC_PORT_C:
		CBUS_SET_CLEAR(PERIPHS_PIN_MUX_2_REG, 0, 0x01000000);
		CBUS_SET_CLEAR(PREG_PAD_GPIO3_EN_N_REG, 0, 0x00000200);
		CBUS_SET_CLEAR(PREG_PAD_GPIO3_OUT_REG, 0x00000200, 0);
		delay(1000);
		CBUS_SET_CLEAR(PREG_PAD_GPIO3_OUT_REG, 0, 0x00000200);
		delay(2000);
		CBUS_SET_CLEAR(PREG_PAD_GPIO3_OUT_REG, 0x00000200, 0);
		delay(1000);
		break;
	}
}

bool
amlogic_sdhc_is_removable(int port)
{
	switch (port) {
	case AMLOGIC_SDHC_PORT_B:
		return true;
	default:
		return false;
	}
}

bool
amlogic_sdhc_is_card_present(int port)
{
	switch (port) {
	case AMLOGIC_SDHC_PORT_B:
		/* GPIO CARD_6 */
		return !(CBUS_READ(PREG_PAD_GPIO0_IN_REG) & __BIT(28));
	default:
		return true;
	}
}

void
amlogic_sdhc_set_voltage(int port, int voltage)
{
	const bus_size_t gpioao_reg = AMLOGIC_GPIOAO_OFFSET;
	bus_space_tag_t bst = &armv7_generic_bs_tag;
	bus_space_handle_t bsh = amlogic_core_bsh;
	uint32_t val;
	u_int pin;

	switch (port) {
	case AMLOGIC_SDHC_PORT_B:
		/* GPIO GPIOAO_3 */
		pin = 3;
		val = bus_space_read_4(bst, bsh, gpioao_reg);
		val &= ~__BIT(pin);	/* OEN */
		bus_space_write_4(bst, bsh, gpioao_reg, val);
		if (voltage == AMLOGIC_SDHC_VOL_180) {
			val |= __BIT(pin + 16);		/* OUT */
		} else {
			val &= ~__BIT(pin + 16);	/* OUT */
		}
		bus_space_write_4(bst, bsh, gpioao_reg, val);
		delay(20000);
		break;
	}
}

void
amlogic_sdio_init(void)
{
	/* enable SDIO clk */
	CBUS_WRITE(EE_CLK_GATING0_REG,
	    CBUS_READ(EE_CLK_GATING0_REG) | EE_CLK_GATING0_SDIO);

	/* reset */
	CBUS_SET_CLEAR(RESET6_REG, RESET6_SDIO, 0);
}

int
amlogic_sdio_select_port(int port)
{
	switch (port) {
	case AMLOGIC_SDIO_PORT_B:
		/* Set CARD 0-5 to input */
		CBUS_SET_CLEAR(PAD_PULL_UP_2_REG, 0x03f00000, 0);
		CBUS_SET_CLEAR(PAD_PULL_UP_EN_2_REG, 0x03f00000, 0);
		CBUS_SET_CLEAR(PREG_PAD_GPIO0_EN_N_REG, 0x0fc00000, 0);

		/* CARD -> SDIO pin mux settings */
		CBUS_SET_CLEAR(PERIPHS_PIN_MUX_6_REG, 0, 0x3f000000);
		CBUS_SET_CLEAR(PERIPHS_PIN_MUX_8_REG, 0, 0x0000063f);
		CBUS_SET_CLEAR(PERIPHS_PIN_MUX_2_REG, 0, 0x000000f0);
		CBUS_SET_CLEAR(PERIPHS_PIN_MUX_2_REG, 0x0000fc00, 0);

		/* XXX ODROID-C1 */
		CBUS_SET_CLEAR(PREG_PAD_GPIO5_EN_N_REG, 0x20000000, 0);
		CBUS_SET_CLEAR(PREG_PAD_GPIO5_OUT_REG, 0, 0x80000000);
		CBUS_SET_CLEAR(PREG_PAD_GPIO5_EN_N_REG, 0, 0x80000000);
		break;
	case AMLOGIC_SDIO_PORT_C:
		delay(100);

		/* Set BOOT 0-8,10 to input */
		CBUS_SET_CLEAR(PAD_PULL_UP_2_REG, 0x0000050f, 0);
		CBUS_SET_CLEAR(PAD_PULL_UP_EN_2_REG, 0x0000050f, 0);
		CBUS_SET_CLEAR(PREG_PAD_GPIO3_EN_N_REG, 0x0000050f, 0);

		/* BOOT -> SDIO pin mux settings */
		CBUS_SET_CLEAR(PERIPHS_PIN_MUX_2_REG, 0, 0x06c2fc00);
		CBUS_SET_CLEAR(PERIPHS_PIN_MUX_8_REG, 0, 0x0000003f);
		CBUS_SET_CLEAR(PERIPHS_PIN_MUX_4_REG, 0, 0x6c000000);
		CBUS_SET_CLEAR(PERIPHS_PIN_MUX_6_REG, 0xfc000000, 0);

		/* XXX ODROID-C1 */
		CBUS_SET_CLEAR(PAD_PULL_UP_3_REG, 0, 0x000000ff);
		break;
	default:
		return EINVAL;
	}

	return 0;
}

static void
amlogic_usbphy_clkgate_enable(int port)
{
	switch (port) {
	case 0:
		CBUS_WRITE(EE_CLK_GATING1_REG,
		    CBUS_READ(EE_CLK_GATING1_REG) |
		    EE_CLK_GATING1_USB_GENERAL |
		    EE_CLK_GATING1_USB0);
		CBUS_WRITE(EE_CLK_GATING2_REG,
		    CBUS_READ(EE_CLK_GATING2_REG) |
		    EE_CLK_GATING2_USB0_TO_DDR);
		break;
	case 1:
		CBUS_WRITE(EE_CLK_GATING1_REG,
		    CBUS_READ(EE_CLK_GATING1_REG) |
		    EE_CLK_GATING1_USB_GENERAL |
		    EE_CLK_GATING1_USB1);
		CBUS_WRITE(EE_CLK_GATING2_REG,
		    CBUS_READ(EE_CLK_GATING2_REG) |
		    EE_CLK_GATING2_USB1_TO_DDR);
		break;
	}
}

void
amlogic_usbphy_init(int port)
{
	bus_space_tag_t bst = &armv7_generic_bs_tag;
	bus_space_handle_t bsh = amlogic_core_bsh;
	bus_size_t ctrl_reg, cfg_reg, adp_bc_reg, gpioao_reg;
	uint32_t ctrl, cfg, adp_bc, gpioao;
	u_int pin, pol;
	bool gpio_power = false, gpio_reset = false, aca_enable = false;

	gpioao_reg = AMLOGIC_GPIOAO_OFFSET;

	switch (port) {
	case 0:
		cfg_reg = PREI_USB_PHY_A_CFG_REG;
		ctrl_reg = PREI_USB_PHY_A_CTRL_REG;
		adp_bc_reg = PREI_USB_PHY_A_ADP_BC_REG;
		pin = 5;
		pol = 1;
		gpio_power = true;
		break;
	case 1:
		cfg_reg = PREI_USB_PHY_B_CFG_REG;
		ctrl_reg = PREI_USB_PHY_B_CTRL_REG;
		adp_bc_reg = PREI_USB_PHY_B_ADP_BC_REG;
		pin = 4;
		pol = 0;
		gpio_reset = true;
		aca_enable = true;
		break;
	default:
		return;
	}

	if (port == 0) {
		CBUS_SET_CLEAR(RESET1_REG, RESET1_USB, 0);
	}

	amlogic_usbphy_clkgate_enable(port);

	if (gpio_power) {
		gpioao = bus_space_read_4(bst, bsh, gpioao_reg);
		gpioao &= ~__BIT(pin);		/* OEN */
		bus_space_write_4(bst, bsh, gpioao_reg, gpioao);
		if (pol) {
			gpioao |= __BIT(pin + 16);	/* OUT */
		} else {
			gpioao &= ~__BIT(pin + 16);	/* OUT */
		}
		bus_space_write_4(bst, bsh, gpioao_reg, gpioao);
	}

	delay(1000);

	cfg = CBUS_READ(cfg_reg);
	cfg |= PREI_USB_PHY_CFG_CLK_32K_ALT_SEL;
	CBUS_WRITE(cfg_reg, cfg);

	ctrl = CBUS_READ(ctrl_reg);
	ctrl &= ~PREI_USB_PHY_CTRL_FSEL;
	ctrl |= __SHIFTIN(PREI_USB_PHY_CTRL_FSEL_24M,
			  PREI_USB_PHY_CTRL_FSEL);
	ctrl |= PREI_USB_PHY_CTRL_POR;
	CBUS_WRITE(ctrl_reg, ctrl);

	delay(1000);

	ctrl = CBUS_READ(ctrl_reg);
	ctrl &= ~PREI_USB_PHY_CTRL_POR;
	CBUS_WRITE(ctrl_reg, ctrl);

	delay(50000);

	ctrl = CBUS_READ(ctrl_reg);

	if ((ctrl & PREI_USB_PHY_CTRL_CLK_DET) == 0)
		printf("WARNING: USB PHY port %d clock not detected\n", port);

	if (aca_enable) {
		adp_bc = CBUS_READ(adp_bc_reg);
		adp_bc |= PREI_USB_PHY_ADP_BC_ACA_ENABLE;
		CBUS_WRITE(adp_bc_reg, adp_bc);

		delay(1000);

		adp_bc = CBUS_READ(adp_bc_reg);
		if (adp_bc & PREI_USB_PHY_ADP_BC_ACA_FLOATING)
			printf("WARNING: USB PHY port %d failed to enable "
			    "ACA detection\n", port);
	}

	if (gpio_reset) {
		/* Reset */
		gpioao = bus_space_read_4(bst, bsh, gpioao_reg);
		gpioao &= ~__BIT(pin);		/* OEN */
		bus_space_write_4(bst, bsh, gpioao_reg, gpioao);
		if (pol) {
			gpioao |= __BIT(pin + 16);	/* OUT */
		} else {
			gpioao &= ~__BIT(pin + 16);	/* OUT */
		}
		bus_space_write_4(bst, bsh, gpioao_reg, gpioao);

		delay(1000);

		if (pol) {
			gpioao &= ~__BIT(pin + 16);	/* OUT */
		} else {
			gpioao |= __BIT(pin + 16);	/* OUT */
		}
		bus_space_write_4(bst, bsh, gpioao_reg, gpioao);

		delay(60000);
	}
}
