/*
 * arch/arm/mach-mcs814x/common.c
 *
 * Core functions for Moschip MCS814x SoCs
 *
 * This file is licensed under the terms of the GNU General Public
 * License version 2.  This program is licensed "as is" without any
 * warranty of any kind, whether express or implied.
 */

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_address.h>

#include <asm/setup.h>
#include <asm/mach-types.h>
#include <asm/mach/arch.h>
#include <mach/mcs814x.h>
#include <mach/cpu.h>
#include <asm/pgtable.h>
#include <asm/mach/map.h>

void __iomem *mcs814x_sysdbg_base;

static struct map_desc mcs814x_io_desc[] __initdata = {
	{
		.virtual	= MCS814X_IO_BASE,
		.pfn		= __phys_to_pfn(MCS814X_IO_START),
		.length		= MCS814X_IO_SIZE,
		.type		= MT_DEVICE
	},
};

struct cpu_mode {
	const char *name;
	int gpio_start;
	int gpio_end;
};

static const struct cpu_mode cpu_modes[] = {
	{
		.name		= "I2S",
		.gpio_start	= 4,
		.gpio_end	= 8,
	},
	{
		.name		= "UART",
		.gpio_start	= 4,
		.gpio_end	= 9,
	},
	{
		.name		= "External MII",
		.gpio_start	= 0,
		.gpio_end	= 16,
	},
	{
		.name		= "Normal",
		.gpio_start	= -1,
		.gpio_end	= -1,
	},
};

static void mcs814x_eth_hardware_filter_set(u8 value)
{
	u32 reg;

	reg = readl_relaxed(MCS814X_VIRT_BASE + MCS814X_DBGLED);
	if (value)
		reg |= 0x80;
	else
		reg &= ~0x80;
	writel_relaxed(reg, MCS814X_VIRT_BASE + MCS814X_DBGLED);
}

static void mcs814x_eth_led_cfg_set(u8 cfg)
{
	u32 reg;

	reg = readl_relaxed(mcs814x_sysdbg_base + SYSDBG_BS2);
	reg &= ~LED_CFG_MASK;
	reg |= cfg;
	writel_relaxed(reg, mcs814x_sysdbg_base + SYSDBG_BS2);
}

static void mcs814x_eth_buffer_shifting_set(u8 value)
{
	u8 reg;

	reg = readb_relaxed(mcs814x_sysdbg_base + SYSDBG_SYSCTL_MAC);
	if (value)
		reg |= BUF_SHIFT_BIT;
	else
		reg &= ~BUF_SHIFT_BIT;
	writeb_relaxed(reg, mcs814x_sysdbg_base + SYSDBG_SYSCTL_MAC);
}

static struct of_device_id mcs814x_eth_ids[] __initdata = {
	{ .compatible = "moschip,nuport-mac", },
	{ /* sentinel */ },
};

/* Configure platform specific knobs based on ethernet device node
 * properties */
static void mcs814x_eth_init(void)
{
	struct device_node *np;
	const unsigned int *intspec;

	np = of_find_matching_node(NULL, mcs814x_eth_ids);
	if (!np)
		return;

	/* hardware filter must always be enabled */
	mcs814x_eth_hardware_filter_set(1);

	intspec = of_get_property(np, "nuport-mac,buffer-shifting", NULL);
	if (!intspec)
		mcs814x_eth_buffer_shifting_set(0);
	else
		mcs814x_eth_buffer_shifting_set(1);

	intspec = of_get_property(np, "nuport-mac,link-activity", NULL);
	if (intspec)
		mcs814x_eth_led_cfg_set(be32_to_cpup(intspec));

	of_node_put(np);
}

void __init mcs814x_init_machine(void)
{
	u32 bs2, cpu_mode;
	int gpio;

	bs2 = readl_relaxed(mcs814x_sysdbg_base + SYSDBG_BS2);
	cpu_mode = (bs2 >> CPU_MODE_SHIFT) & CPU_MODE_MASK;

	pr_info("CPU mode: %s\n", cpu_modes[cpu_mode].name);

	/* request the gpios since the pins are muxed for functionnality */
	for (gpio = cpu_modes[cpu_mode].gpio_start;
		gpio == cpu_modes[cpu_mode].gpio_end; gpio++) {
		if (gpio != -1)
			gpio_request(gpio, cpu_modes[cpu_mode].name);
	}

	mcs814x_eth_init();
}

void __init mcs814x_map_io(void)
{
	iotable_init(mcs814x_io_desc, ARRAY_SIZE(mcs814x_io_desc));

	mcs814x_sysdbg_base = ioremap(MCS814X_IO_START + MCS814X_SYSDBG,
					MCS814X_SYSDBG_SIZE);
	if (!mcs814x_sysdbg_base)
		panic("unable to remap sysdbg base");
}

void mcs814x_restart(char mode, const char *cmd)
{
	writel_relaxed(~(1 << 31), mcs814x_sysdbg_base);
}