/*
 * armadillo 800 eva board support
 *
 * Copyright (C) 2012 Renesas Solutions Corp.
 * Copyright (C) 2012 Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/gpio_keys.h>
#include <linux/regulator/fixed.h>
#include <linux/regulator/machine.h>
#include <linux/sh_eth.h>
#include <linux/videodev2.h>
#include <linux/usb/renesas_usbhs.h>
#include <linux/mfd/tmio.h>
#include <linux/mmc/host.h>
#include <linux/mmc/sh_mmcif.h>
#include <linux/mmc/sh_mobile_sdhi.h>
#include <linux/i2c-gpio.h>
#include <mach/common.h>
#include <mach/irqs.h>
#include <mach/r8a7740.h>
#include <media/mt9t112.h>
#include <media/sh_mobile_ceu.h>
#include <media/soc_camera.h>
#include <asm/page.h>
#include <asm/mach-types.h>
#include <asm/mach/arch.h>
#include <asm/mach/map.h>
#include <asm/mach/time.h>
#include <asm/hardware/cache-l2x0.h>
#include <video/sh_mobile_lcdc.h>
#include <video/sh_mobile_hdmi.h>
#include <sound/sh_fsi.h>
#include <sound/simple_card.h>

#include "sh-gpio.h"

/*
 * CON1		Camera Module
 * CON2		Extension Bus
 * CON3		HDMI Output
 * CON4		Composite Video Output
 * CON5		H-UDI JTAG
 * CON6		ARM JTAG
 * CON7		SD1
 * CON8		SD2
 * CON9		RTC BackUp
 * CON10	Monaural Mic Input
 * CON11	Stereo Headphone Output
 * CON12	Audio Line Output(L)
 * CON13	Audio Line Output(R)
 * CON14	AWL13 Module
 * CON15	Extension
 * CON16	LCD1
 * CON17	LCD2
 * CON19	Power Input
 * CON20	USB1
 * CON21	USB2
 * CON22	Serial
 * CON23	LAN
 * CON24	USB3
 * LED1		Camera LED(Yellow)
 * LED2		Power LED (Green)
 * ED3-LED6	User LED(Yellow)
 * LED7		LAN link LED(Green)
 * LED8		LAN activity LED(Yellow)
 */

/*
 * DipSwitch
 *
 *                    SW1
 *
 * -12345678-+---------------+----------------------------
 *  1        | boot          | hermit
 *  0        | boot          | OS auto boot
 * -12345678-+---------------+----------------------------
 *   00      | boot device   | eMMC
 *   10      | boot device   | SDHI0 (CON7)
 *   01      | boot device   | -
 *   11      | boot device   | Extension Buss (CS0)
 * -12345678-+---------------+----------------------------
 *     0     | Extension Bus | D8-D15 disable, eMMC enable
 *     1     | Extension Bus | D8-D15 enable,  eMMC disable
 * -12345678-+---------------+----------------------------
 *      0    | SDHI1         | COM8 disable, COM14 enable
 *      1    | SDHI1         | COM8 enable,  COM14 disable
 * -12345678-+---------------+----------------------------
 *       0   | USB0          | COM20 enable,  COM24 disable
 *       1   | USB0          | COM20 disable, COM24 enable
 * -12345678-+---------------+----------------------------
 *        00 | JTAG          | SH-X2
 *        10 | JTAG          | ARM
 *        01 | JTAG          | -
 *        11 | JTAG          | Boundary Scan
 *-----------+---------------+----------------------------
 */

/*
 * FSI-WM8978
 *
 * this command is required when playback.
 *
 * # amixer set "Headphone" 50
 */

/*
 * USB function
 *
 * When you use USB Function,
 * set SW1.6 ON, and connect cable to CN24.
 *
 * USBF needs workaround on R8A7740 chip.
 * These are a little bit complex.
 * see
 *	usbhsf_power_ctrl()
 */
#define IRQ7		evt2irq(0x02e0)
#define USBCR1		IOMEM(0xe605810a)
#define USBH		0xC6700000
#define USBH_USBCTR	0x10834

struct usbhsf_private {
	struct clk *phy;
	struct clk *usb24;
	struct clk *pci;
	struct clk *func;
	struct clk *host;
	void __iomem *usbh_base;
	struct renesas_usbhs_platform_info info;
};

#define usbhsf_get_priv(pdev)				\
	container_of(renesas_usbhs_get_info(pdev),	\
		     struct usbhsf_private, info)

static int usbhsf_get_id(struct platform_device *pdev)
{
	return USBHS_GADGET;
}

static void usbhsf_power_ctrl(struct platform_device *pdev,
			      void __iomem *base, int enable)
{
	struct usbhsf_private *priv = usbhsf_get_priv(pdev);

	/*
	 * Work around for USB Function.
	 * It needs USB host clock, and settings
	 */
	if (enable) {
		/*
		 * enable all the related usb clocks
		 * for usb workaround
		 */
		clk_enable(priv->usb24);
		clk_enable(priv->pci);
		clk_enable(priv->host);
		clk_enable(priv->func);
		clk_enable(priv->phy);

		/*
		 * set USBCR1
		 *
		 * Port1 is driven by USB function,
		 * Port2 is driven by USB HOST
		 * One HOST (Port1 or Port2 is HOST)
		 * USB PLL input clock = 24MHz
		 */
		__raw_writew(0xd750, USBCR1);
		mdelay(1);

		/*
		 * start USB Host
		 */
		__raw_writel(0x0000000c, priv->usbh_base + USBH_USBCTR);
		__raw_writel(0x00000008, priv->usbh_base + USBH_USBCTR);
		mdelay(10);

		/*
		 * USB PHY Power ON
		 */
		__raw_writew(0xd770, USBCR1);
		__raw_writew(0x4000, base + 0x102); /* USBF :: SUSPMODE */

	} else {
		__raw_writel(0x0000010f, priv->usbh_base + USBH_USBCTR);
		__raw_writew(0xd7c0, USBCR1); /* GPIO */

		clk_disable(priv->phy);
		clk_disable(priv->func);	/* usb work around */
		clk_disable(priv->host);	/* usb work around */
		clk_disable(priv->pci);		/* usb work around */
		clk_disable(priv->usb24);	/* usb work around */
	}
}

static int usbhsf_get_vbus(struct platform_device *pdev)
{
	return gpio_get_value(GPIO_PORT209);
}

static irqreturn_t usbhsf_interrupt(int irq, void *data)
{
	struct platform_device *pdev = data;

	renesas_usbhs_call_notify_hotplug(pdev);

	return IRQ_HANDLED;
}

static void usbhsf_hardware_exit(struct platform_device *pdev)
{
	struct usbhsf_private *priv = usbhsf_get_priv(pdev);

	if (!IS_ERR(priv->phy))
		clk_put(priv->phy);
	if (!IS_ERR(priv->usb24))
		clk_put(priv->usb24);
	if (!IS_ERR(priv->pci))
		clk_put(priv->pci);
	if (!IS_ERR(priv->host))
		clk_put(priv->host);
	if (!IS_ERR(priv->func))
		clk_put(priv->func);
	if (priv->usbh_base)
		iounmap(priv->usbh_base);

	priv->phy	= NULL;
	priv->usb24	= NULL;
	priv->pci	= NULL;
	priv->host	= NULL;
	priv->func	= NULL;
	priv->usbh_base	= NULL;

	free_irq(IRQ7, pdev);
}

static int usbhsf_hardware_init(struct platform_device *pdev)
{
	struct usbhsf_private *priv = usbhsf_get_priv(pdev);
	int ret;

	priv->phy	= clk_get(&pdev->dev, "phy");
	priv->usb24	= clk_get(&pdev->dev, "usb24");
	priv->pci	= clk_get(&pdev->dev, "pci");
	priv->func	= clk_get(&pdev->dev, "func");
	priv->host	= clk_get(&pdev->dev, "host");
	priv->usbh_base	= ioremap_nocache(USBH, 0x20000);

	if (IS_ERR(priv->phy)		||
	    IS_ERR(priv->usb24)		||
	    IS_ERR(priv->pci)		||
	    IS_ERR(priv->host)		||
	    IS_ERR(priv->func)		||
	    !priv->usbh_base) {
		dev_err(&pdev->dev, "USB clock setting failed\n");
		usbhsf_hardware_exit(pdev);
		return -EIO;
	}

	ret = request_irq(IRQ7, usbhsf_interrupt, IRQF_TRIGGER_NONE,
			  dev_name(&pdev->dev), pdev);
	if (ret) {
		dev_err(&pdev->dev, "request_irq err\n");
		return ret;
	}
	irq_set_irq_type(IRQ7, IRQ_TYPE_EDGE_BOTH);

	/* usb24 use 1/1 of parent clock (= usb24s = 24MHz) */
	clk_set_rate(priv->usb24,
		     clk_get_rate(clk_get_parent(priv->usb24)));

	return 0;
}

static struct usbhsf_private usbhsf_private = {
	.info = {
		.platform_callback = {
			.get_id		= usbhsf_get_id,
			.get_vbus	= usbhsf_get_vbus,
			.hardware_init	= usbhsf_hardware_init,
			.hardware_exit	= usbhsf_hardware_exit,
			.power_ctrl	= usbhsf_power_ctrl,
		},
		.driver_param = {
			.buswait_bwait		= 5,
			.detection_delay	= 5,
			.d0_rx_id	= SHDMA_SLAVE_USBHS_RX,
			.d1_tx_id	= SHDMA_SLAVE_USBHS_TX,
		},
	}
};

static struct resource usbhsf_resources[] = {
	{
		.name	= "USBHS",
		.start	= 0xe6890000,
		.end	= 0xe6890104 - 1,
		.flags	= IORESOURCE_MEM,
	},
	{
		.start	= evt2irq(0x0A20),
		.flags	= IORESOURCE_IRQ,
	},
};

static struct platform_device usbhsf_device = {
	.name	= "renesas_usbhs",
	.dev = {
		.platform_data = &usbhsf_private.info,
	},
	.id = -1,
	.num_resources	= ARRAY_SIZE(usbhsf_resources),
	.resource	= usbhsf_resources,
};

/* Ether */
static struct sh_eth_plat_data sh_eth_platdata = {
	.phy			= 0x00, /* LAN8710A */
	.edmac_endian		= EDMAC_LITTLE_ENDIAN,
	.register_type		= SH_ETH_REG_GIGABIT,
	.phy_interface		= PHY_INTERFACE_MODE_MII,
};

static struct resource sh_eth_resources[] = {
	{
		.start	= 0xe9a00000,
		.end	= 0xe9a00800 - 1,
		.flags	= IORESOURCE_MEM,
	}, {
		.start	= 0xe9a01800,
		.end	= 0xe9a02000 - 1,
		.flags	= IORESOURCE_MEM,
	}, {
		.start	= evt2irq(0x0500),
		.flags	= IORESOURCE_IRQ,
	},
};

static struct platform_device sh_eth_device = {
	.name = "sh-eth",
	.id = -1,
	.dev = {
		.platform_data = &sh_eth_platdata,
	},
	.resource = sh_eth_resources,
	.num_resources = ARRAY_SIZE(sh_eth_resources),
};

/* LCDC */
static struct fb_videomode lcdc0_mode = {
	.name		= "AMPIER/AM-800480",
	.xres		= 800,
	.yres		= 480,
	.left_margin	= 88,
	.right_margin	= 40,
	.hsync_len	= 128,
	.upper_margin	= 20,
	.lower_margin	= 5,
	.vsync_len	= 5,
	.sync		= 0,
};

static struct sh_mobile_lcdc_info lcdc0_info = {
	.clock_source	= LCDC_CLK_BUS,
	.ch[0] = {
		.chan		= LCDC_CHAN_MAINLCD,
		.fourcc		= V4L2_PIX_FMT_RGB565,
		.interface_type	= RGB24,
		.clock_divider	= 5,
		.flags		= 0,
		.lcd_modes	= &lcdc0_mode,
		.num_modes	= 1,
		.panel_cfg = {
			.width	= 111,
			.height = 68,
		},
	},
};

static struct resource lcdc0_resources[] = {
	[0] = {
		.name	= "LCD0",
		.start	= 0xfe940000,
		.end	= 0xfe943fff,
		.flags	= IORESOURCE_MEM,
	},
	[1] = {
		.start	= intcs_evt2irq(0x580),
		.flags	= IORESOURCE_IRQ,
	},
};

static struct platform_device lcdc0_device = {
	.name		= "sh_mobile_lcdc_fb",
	.num_resources	= ARRAY_SIZE(lcdc0_resources),
	.resource	= lcdc0_resources,
	.id		= 0,
	.dev	= {
		.platform_data	= &lcdc0_info,
		.coherent_dma_mask = ~0,
	},
};

/*
 * LCDC1/HDMI
 */
static struct sh_mobile_hdmi_info hdmi_info = {
	.flags		= HDMI_OUTPUT_PUSH_PULL |
			  HDMI_OUTPUT_POLARITY_HI |
			  HDMI_32BIT_REG |
			  HDMI_HAS_HTOP1 |
			  HDMI_SND_SRC_SPDIF,
};

static struct resource hdmi_resources[] = {
	[0] = {
		.name	= "HDMI",
		.start	= 0xe6be0000,
		.end	= 0xe6be03ff,
		.flags	= IORESOURCE_MEM,
	},
	[1] = {
		.start	= evt2irq(0x1700),
		.flags	= IORESOURCE_IRQ,
	},
	[2] = {
		.name	= "HDMI emma3pf",
		.start	= 0xe6be4000,
		.end	= 0xe6be43ff,
		.flags	= IORESOURCE_MEM,
	},
};

static struct platform_device hdmi_device = {
	.name		= "sh-mobile-hdmi",
	.num_resources	= ARRAY_SIZE(hdmi_resources),
	.resource	= hdmi_resources,
	.id             = -1,
	.dev	= {
		.platform_data	= &hdmi_info,
	},
};

static const struct fb_videomode lcdc1_mode = {
	.name		= "HDMI 720p",
	.xres		= 1280,
	.yres		= 720,
	.pixclock	= 13468,
	.left_margin	= 220,
	.right_margin	= 110,
	.hsync_len	= 40,
	.upper_margin	= 20,
	.lower_margin	= 5,
	.vsync_len	= 5,
	.refresh	= 60,
	.sync		= FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT,
};

static struct sh_mobile_lcdc_info hdmi_lcdc_info = {
	.clock_source	= LCDC_CLK_PERIPHERAL, /* HDMI clock */
