/* $NetBSD: amlogic_board.c,v 1.9.2.3 2015/04/06 01:43:31 snj 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.9.2.3 2015/04/06 01:43:31 snj Exp $");

#define	_ARM32_BUS_DMA_PRIVATE
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/cpu.h>
#include <sys/device.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>

bus_space_handle_t amlogic_core_bsh;

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(&amlogic_bs_tag, amlogic_core_bsh, \
			 AMLOGIC_CBUS_OFFSET + (x))
#define CBUS_WRITE(x, v)	\
	bus_space_write_4(&amlogic_bs_tag, amlogic_core_bsh, \
			  AMLOGIC_CBUS_OFFSET + (x), (v))

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

void
amlogic_bootstrap(void)
{
	int error;

	error = bus_space_map(&amlogic_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_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_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:
		/* 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);
		break;
	case AMLOGIC_SDHC_PORT_C:
		/* 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);
		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 = &amlogic_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_WRITE(RESET1_REG, RESET1_USB);
	}

	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);
	}
}
