/*--------------------------------------------------------------------------*
 * drivers/usb/mcd/usb_mcd_board_cxd4105.h
 * 
 * USB controller driver
 * 
 * Copyright 2009 Sony Corporation
 *
 * This file is part of the Inventra Controller Driver for Linux.
 * 
 * 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; either version 2
 * of the License, or (at your option) any later version.
 *
 * 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 Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 *---------------------------------------------------------------------------*/

/************************** macro, variable definitions ***********************/

/*
 * device情報
 */
/* アドレス */
#define ADR_START	0x70200000
#define ADR_END  	0x70201000

/* 割り込み */
#define IRQ_USB0	IRQ_USB_HS
#define IRQ_USB1	IRQ_USB_DMAC

/*
 * タイマー制御
 */
#define VA_TIMER(x)	TIMER_VA_BASE(x)
/* Timer ch */
#define TIMER_FOR_USB	3

/*
 * PPARAM
 */
#if !defined(CONFIG_ARCH_MB8AC102)
#define BOARD_PPARAM	{ 0x24, 0x22, 0x92, 0x04, 0x24, 0x89 }
#define BOARD_PPARAM_NUM 6
#else
#define BOARD_PPARAM	{ \
	0x24, 0x20, 0x92, 0x04, 0x24, 0x09, 0x08, 0x78, 0x17, 0x00 }
#define BOARD_PPARAM_NUM 10
#endif


/*
 * クロック制御bit
 */
#define B_USBPHYCK	(1UL<<0)
#define B_PSHCLKUSB	(1UL<<0)
#define B_RSTUSB	(1UL<<0)

#define VA_GPIO(x)	IO_ADDRESSP(0x76701000 + 0x100 * (x))
#define _GPIO17		VA_GPIO(17)
#define _GPIO18		VA_GPIO(18)
#define _GPIO19		VA_GPIO(19)
#define _GPIOEASY3	IO_ADDRESSP(0x76700300)
#if defined(CONFIG_ARCH_MB8AC102)
#define _GPIO2_4	IO_ADDRESSP(0x76709400)
#define _GPIO2_5	IO_ADDRESSP(0x76709500)
#endif

/*
 * timeout for PHY lock
 */
#define TIMEOUT_FOR_PHY_LOCK	100 /* PHY PLL lock time 83[us] */

/*  _GPIO_SET_MON ... 1bitデータモニター入力の設定 */
#define _GPIO_SET_MON(NR, BIT) { \
	unsigned short reg; \
	reg = readl((NR)+0x00); /* 方向取得 */ \
	reg &= ~(1<<(BIT));	/* 入力方向 */ \
	writel(reg, (NR)+0x00); /* 方向設定 */ \
}

/*  _GPIO_SET_IN ... 1bitデータ入力の設定 */
#define _GPIO_SET_IN(NR, BIT) { \
	unsigned short reg; \
	reg = readl((NR)+0x00); /* 方向取得 */ \
	reg &= ~(1<<(BIT));         /* 入力方向 */ \
	writel(reg, _gpio_log_adrs[(NR)]+0x00); /* 方向設定 */ \
	writel(0, (NR)+0x14+(BIT)*4); /* 汎用ポート */ \
}

/*  _GPIO_SET_OUT ... 1bitデータ出力 */
#define _GPIO_SET_OUT(NR, BIT, DATA) { \
	unsigned short reg; \
	writel(0, (NR)+0x14+(BIT)*4); /* 汎用ポート */ \
	reg = 1<<(BIT);             /* 出力データ */ \
	/* DATAがON時はGPIODATASetし, OFF時はGPIODATAClear */ \
	writel(reg, (NR)+((DATA)?0x08:0x0c)); \
	reg = readl((NR)+0x00); /* 方向取得 */ \
	reg |= 1<<(BIT);            /* 出力方向 */ \
	writel(reg, (NR)+0x00); /* 方向設定 */ \
}

/*  _GPIO_SET_FUNC ... 1bit機能ピン設定 */
#define _GPIO_SET_FUNC(NR, BIT) { \
	unsigned short reg; \
	writel(1, (NR)+0x14+(BIT)*4); /* 機能ポート */ \
	reg = readl((NR)+0x00); /* 方向取得 */ \
	reg &= ~(1<<(BIT));         /* 入力方向 */ \
	writel(reg, (NR)+0x00); /* 方向設定 */ \
}

/*  _GPIO_CHECK_FUNC ... 1bit機能ピン設定確認 */
#define _GPIO_CHECK_FUNC(NR, BIT) (readl((NR)+0x14+(BIT)*4))

/*  _GPIO_READ ... 1bitデータ入力 */
#define _GPIO_READ(NR, BIT)    (1&((readl((NR)+0x04))>>(BIT)))

/*  _GPIO_READ32 ... 32bitデータ入力 */
#define _GPIO_READ32(NR)  (readl((NR)+0x04))

/*  _GPIO_SET_PPARAM ... PPARAMデータの設定 */
#define _GPIO_SET_PPARAM(NR, DATA) { \
	writel(0xffff, (NR)+0x00); /* 出力方向 */ \
	writel((DATA), (NR)+0x08); /* 出力データ */ \
	writel(~(DATA), (NR)+0x0c); /* 出力データ */ \
}

/*  _GPIO_GET_PPARAM ... PPARAMデータの取得 */
#define _GPIO_GET_PPARAM(NR)  (readl((NR)+0x04))

/*  _GPIO_GET_STAT ... 1bit状態取得 */
#define _GPIO_GET_STAT(NR, BIT) \
	(_GPIO_CHECK_FUNC((NR), (BIT)) ? USB_OTG_THRU \
	 : _GPIO_READ((NR), (BIT)) ? USB_OTG_ON : USB_OTG_OFF)

/***************************** internal functions *****************************/

/* PHY CLK ON制御 */
static inline void phy_clock_on(void)
{
	u8 usbphycken = readb(VA_CLKBLK_BASE + CLKBLK_USBPHYCKEN);
	DBG(3, "READ usbphycken=%x\n", usbphycken);
	if (!(usbphycken & B_USBPHYCK)) {
		writeb(B_USBPHYCK, VA_CLKBLK_BASE + CLKBLK_USBPHYCKEN);
		DBG(3, "SET usbphycken=%x\n",
		    readb(VA_CLKBLK_BASE + CLKBLK_USBPHYCKEN));
	}
	_GPIO_SET_OUT(_GPIOEASY3, 8, ON); /* Suspend機能OFF */
	udelay(100);	/* PHY PLL lock time 83[us] */
}

/* phy_reset phyリセット */
static inline void phy_reset(void)
{
	_GPIO_SET_OUT(_GPIOEASY3, 11, OFF);
	udelay(3);
	_GPIO_SET_OUT(_GPIOEASY3, 11, ON);
}

/* LINK CLK OFF制御 */
static void link_clock_off(void)
{
	u8 pshclkusb = readb(VA_CLKBLK_BASE + CLKBLK_PSHCLKUSB);
	DBG(3, "READ pshclkusb=%x\n", pshclkusb);
	if (pshclkusb & B_PSHCLKUSB) {
		writeb(0, VA_CLKBLK_BASE + CLKBLK_PSHCLKUSB);
		udelay(1);
		writeb(0, VA_CLKBLK_BASE + CLKBLK_RSTUSB);
		udelay(1);
		DBG(3, "SET pshclkusb=%x\n",
		    readb(VA_CLKBLK_BASE + CLKBLK_PSHCLKUSB));
	}
}

/* LINK CLK ON制御 */
static void link_clock_on(void)
{
	u8 pshclkusb = readb(VA_CLKBLK_BASE + CLKBLK_PSHCLKUSB);
	DBG(3, "READ pshclkusb=%x\n", pshclkusb);
	if (!(pshclkusb & B_PSHCLKUSB)) {
		writeb(B_RSTUSB, VA_CLKBLK_BASE + CLKBLK_RSTUSB);
		udelay(1);
		writeb(B_PSHCLKUSB, VA_CLKBLK_BASE + CLKBLK_PSHCLKUSB);
		udelay(1);
		DBG(3, "SET pshclkusb=%x\n",
		    readb(VA_CLKBLK_BASE + CLKBLK_PSHCLKUSB));
	}
}

/* all clock off */
static inline void clock_off(void)
{
	DBG(2, "<==\n");
	_GPIO_SET_OUT(_GPIOEASY3, 8, OFF); /* SUSPEND# Suspend設定 */
	link_clock_off();
	DBG(2, "==>\n");
}

/* all clock on */
static inline void clock_on(void)
{
	DBG(2, "<==\n");
	link_clock_on();
	_GPIO_SET_FUNC(_GPIOEASY3, 8); /* SUSPEND# 機能ピン設定 */
	DBG(2, "==>\n");
}

/* check clock activity */
static inline u8 check_clock(void)
{
	u8 pshclkusb = readb(VA_CLKBLK_BASE + CLKBLK_PSHCLKUSB);
	return pshclkusb & B_PSHCLKUSB ? USB_OTG_ON : USB_OTG_OFF;
}

/* set_pparam ... set pparam */
static inline int set_pparam(u8 *data)
{
	_GPIO_SET_PPARAM(_GPIO17, data[1] << 8 | data[0]);
	_GPIO_SET_PPARAM(_GPIO18, data[3] << 8 | data[2]);
	_GPIO_SET_PPARAM(_GPIO19, data[5] << 8 | data[4]);
#if defined(CONFIG_ARCH_MB8AC102)
	_GPIO_SET_PPARAM(_GPIO2_4, data[7] << 8 | data[6]);
	_GPIO_SET_PPARAM(_GPIO2_5, data[9] << 8 | data[8]);
#endif
	DBG(2, "GPIO17 0x%x\n", _GPIO_GET_PPARAM(_GPIO17));
	DBG(2, "GPIO18 0x%x\n", _GPIO_GET_PPARAM(_GPIO18));
	DBG(2, "GPIO19 0x%x\n", _GPIO_GET_PPARAM(_GPIO19));
#if defined(CONFIG_ARCH_MB8AC102)
	DBG(2, "GPIO2_4 0x%x\n", _GPIO_GET_PPARAM(_GPIO2_4));
	DBG(2, "GPIO2_5 0x%x\n", _GPIO_GET_PPARAM(_GPIO2_5));
#endif
	return 0;
}

/**************************** check functions *****************************/

static inline bool read_iddig(void)
{
	return (bool)_GPIO_READ(_GPIOEASY3, 3);
}

static inline void control_iddig_b(void)
{
	_GPIO_SET_OUT(_GPIOEASY3, 3, ON);
}

static inline void control_iddig_a(void)
{
	_GPIO_SET_OUT(_GPIOEASY3, 3, OFF);
}


static inline void control_iddig_thru(void)
{
	_GPIO_SET_FUNC(_GPIOEASY3, 3);
}

static inline enum usb_otg_vbus_stat read_vbusin(void)
{
	u32 data;
	enum usb_otg_vbus_stat vbus_stat;

	/* Vbus GPIO検出しない場合はGPIOEASY3にてVbus検知 */
	data = _GPIO_READ32(_GPIOEASY3) & 0x7;
	/* data = { VBUSVALID, AVALID, SESSEND } by UTMI spec. */
	switch (data) {
	case 0x1:
		vbus_stat = USB_OTG_VBUS_STAT_OFF;
		break;
	case 0x0:
		vbus_stat = USB_OTG_VBUS_STAT_LOW;
		break;
	case 0x2:
		vbus_stat = USB_OTG_VBUS_STAT_SESS;
		break;
	case 0x6:
		vbus_stat = USB_OTG_VBUS_STAT_VALID;
		break;
	default:
		vbus_stat = USB_OTG_VBUS_STAT_UNKNOWN;
		DBG(2, "Unkonwn vbus stat=%d", data);
	}

	return vbus_stat;
}

static inline void control_vbusin_on(void)
{
	_GPIO_SET_OUT(_GPIOEASY3, 0, OFF); /* SESSEND */
	_GPIO_SET_OUT(_GPIOEASY3, 1, ON); /* AVALID */
	_GPIO_SET_OUT(_GPIOEASY3, 2, ON); /* VBUSVALID */
}

static inline void control_vbusin_off(void)
{
	_GPIO_SET_OUT(_GPIOEASY3, 0, ON);	/* SESSEND */
	_GPIO_SET_OUT(_GPIOEASY3, 1, OFF);	/* AVALID */
	_GPIO_SET_OUT(_GPIOEASY3, 2, OFF);	/* VBUSVALID */
}

static inline void control_vbusin_thru(void)
{
	_GPIO_SET_FUNC(_GPIOEASY3, 0); /* SESSEND */
	_GPIO_SET_FUNC(_GPIOEASY3, 1); /* AVALID */
	_GPIO_SET_FUNC(_GPIOEASY3, 2); /* VBUSVALID */
}

static inline u8 check_vbusin(void)
{
	u8 sessend;
	u8 avalid;
	u8 vbusvalid;

	sessend   = _GPIO_GET_STAT(_GPIOEASY3, 0);
	avalid    = _GPIO_GET_STAT(_GPIOEASY3, 1);
	vbusvalid = _GPIO_GET_STAT(_GPIOEASY3, 2);

	if (sessend   == USB_OTG_THRU &&
	    avalid    == USB_OTG_THRU &&
	    vbusvalid == USB_OTG_THRU) {
		return USB_OTG_THRU;
	}
	else if (sessend   == USB_OTG_ON &&
		 avalid    == USB_OTG_OFF &&
		 vbusvalid == USB_OTG_OFF) {
		return USB_OTG_OFF;
	}

	return USB_OTG_ON;
}

static inline void control_vcmpen_on(void)
{
	_GPIO_SET_OUT(_GPIOEASY3, 6, OFF); /* VCMPEN# OFF - 有効 */
}

static inline void control_vcmpen_off(void)
{
	_GPIO_SET_OUT(_GPIOEASY3, 6, ON); /* VCMPEN# ON - 無効 */
}

static inline bool read_suspend(void)
{
	return !(_GPIO_READ(_GPIOEASY3, 8));
}

static inline void control_suspend_off(void)
{
	_GPIO_SET_OUT(_GPIOEASY3, 8, ON); /* Suspend機能OFF */
}

static inline void control_suspend_on(void)
{
	_GPIO_SET_OUT(_GPIOEASY3, 8, OFF); /* Suspend機能ON */
}

static inline void control_suspend_thru(void)
{
	_GPIO_SET_FUNC(_GPIOEASY3, 8); /* Suspend機能機能ピン設定 */
}

static inline void control_idpullup_off(void)
{
	_GPIO_SET_OUT(_GPIOEASY3, 7, OFF);
}

static inline void control_idpullup_on(void)
{
	_GPIO_SET_OUT(_GPIOEASY3, 7, ON);
}

static inline bool check_gpio_sys(gpio_portid prt)
{
	return (prt == GPIO_PORT_17);
}

static inline void control_linestate_set(void)
{
#if defined(CONFIG_ARCH_MB8AC102)
	_GPIO_SET_OUT(_GPIOEASY3, 4, ON );
	writel( 0x0001, _GPIOEASY3 + 0x14+(4)*4 );
#endif
}

static inline void dump_misc(void)
{
	printk(" GPIOEASY3/Data   0x%.4x\n", _GPIO_READ32(_GPIOEASY3));

	printk(" GPIO17           0x%.4x\n", _GPIO_GET_PPARAM(_GPIO17));
	printk(" GPIO18           0x%.4x\n", _GPIO_GET_PPARAM(_GPIO18));
	printk(" GPIO19           0x%.4x\n", _GPIO_GET_PPARAM(_GPIO19));
#if defined(CONFIG_ARCH_MB8AC102)
	printk(" GPIO2_4          0x%.4x\n", _GPIO_GET_PPARAM(_GPIO2_4));
	printk(" GPIO2_5          0x%.4x\n", _GPIO_GET_PPARAM(_GPIO2_5));
#endif
}

#if defined(CONFIG_USB_MCD_HOST)
static int set_gpio_func(gpio_portid, gpio_bitnum, gpio_mode);
static int set_gpio_func(gpio_portid, gpio_bitnum, gpio_mode);

static inline void init_vbusout(void)
{
	/* Vbus出力機能ピン設定 */
	/* VBUSCHG */
	set_gpio_func(GPIO_PORT_2, 8, GPIOMODE_PERIPHERAL);
	/* VBUSDISCHG */
	set_gpio_func(GPIO_PORT_2, 9, GPIOMODE_PERIPHERAL);
	/* VBUSEN */
	set_gpio_func(GPIO_PORT_2, 10, GPIOMODE_OUTPUT_L);
}

static inline void exit_vbusout(void)
{
	/* Vbus出力機能ピン設定 */
	/* VBUSCHG */
	set_gpio_func(GPIO_PORT_2, 8, GPIOMODE_OUTPUT_L);
	/* VBUSDISCHG */
	set_gpio_func(GPIO_PORT_2, 9, GPIOMODE_OUTPUT_L);
	/* VBUSEN */
	set_gpio_func(GPIO_PORT_2, 10, GPIOMODE_OUTPUT_L);
}

static inline void enable_vbusout(void)
{
	set_gpio_func(GPIO_PORT_2, 10, GPIOMODE_OUTPUT_H);
}

static inline void disable_vbusout(void)
{
	set_gpio_func(GPIO_PORT_2, 10, GPIOMODE_OUTPUT_L);
}
#endif				/* defined(CONFIG_USB_MCD_HOST) */

static inline void set_monitor(void)
{
	/* GPIOモニター設定 */
	_GPIO_SET_MON(_GPIOEASY3, 4);	// LINESTATE
	_GPIO_SET_MON(_GPIOEASY3, 5);	// LINESTATE
	_GPIO_SET_MON(_GPIOEASY3, 8);	// SUSPEND#
	_GPIO_SET_MON(_GPIOEASY3, 9);	// OPMODE
	_GPIO_SET_MON(_GPIOEASY3, 10);	// OPMODE
}

static inline void suspend_save(struct _cxd41xx_suspend_save *save)
{
	save->clock     = check_clock();
	save->iddig     = _GPIO_GET_STAT(_GPIOEASY3, 3);
	save->vbusin    = check_vbusin();
	save->vcmpen    = _GPIO_GET_STAT(_GPIOEASY3, 6);
	save->suspend   = _GPIO_GET_STAT(_GPIOEASY3, 8);
	save->idpullup  = _GPIO_GET_STAT(_GPIOEASY3, 7);
}

static inline void resume_restore(struct _cxd41xx_suspend_save *save,
				  u8 *_pparam)
{
	if (save->clock == USB_OTG_ON) {
		clock_on();
	}
	else {
		clock_off();
	}

	if (save->iddig == USB_OTG_ON) {
		control_iddig_b();
	}
	else if (save->iddig == USB_OTG_OFF) {
		control_iddig_a();
	} 
	else {
		control_iddig_thru();
	}

	if (save->vbusin == USB_OTG_ON) {
		control_vbusin_on();
	}
	else if (save->vbusin == USB_OTG_OFF) {
		control_vbusin_off();
	} 
	else {
		control_vbusin_thru();
	}

	if (save->vcmpen == USB_OTG_ON) {
		control_vcmpen_on();
	}
	else {
		control_vcmpen_off();
	}

	if (save->suspend == USB_OTG_ON) {
		control_suspend_on();
	}
	else if (save->suspend == USB_OTG_OFF) {
		control_suspend_off();
	} 
	else {
		control_suspend_thru();
	}

	if (save->idpullup == USB_OTG_ON) {
		control_idpullup_on();
	}
	else {
		control_idpullup_off();
	}

	set_monitor();
	set_pparam(_pparam);
}

static inline void cxd41xx_init(u8 *data)
{
	u8 _pparam[] = BOARD_PPARAM;

	/* PPARAM設定 */
	memcpy(data, _pparam, BOARD_PPARAM_NUM);

	set_monitor();
}

static inline void cxd41xx_exit(void)
{
}
