Commit 1c96293e authored by Kuninori Morimoto's avatar Kuninori Morimoto Committed by Rafael J. Wysocki
Browse files

ARM: mach-shmobile: armadillo800eva: add USB function support

parent 46cf6687
Loading
Loading
Loading
Loading
+237 −10
Original line number Diff line number Diff line
@@ -20,14 +20,17 @@
 */

#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/sh_eth.h>
#include <linux/videodev2.h>
#include <linux/usb/renesas_usbhs.h>
#include <mach/common.h>
#include <mach/irqs.h>
#include <asm/page.h>
@@ -90,6 +93,9 @@
 *      0    | SDHI1         | COM8 enable,  COM14 disable
 *      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          | -
@@ -97,6 +103,195 @@
 *-----------+---------------+----------------------------
 */

/*
 * 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()
 *
 * CAUTION
 *
 * It uses autonomy mode for USB hotplug at this point
 * (= usbhs_private.platform_callback.get_vbus is NULL),
 * since we don't know what's happen on PM control
 * on this workaround.
 */
#define USBCR1		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 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;
}

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

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

	/* 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,
			.hardware_init	= usbhsf_hardware_init,
			.hardware_exit	= usbhsf_hardware_exit,
			.power_ctrl	= usbhsf_power_ctrl,
		},
		.driver_param = {
			.buswait_bwait		= 5,
			.detection_delay	= 5,
		},
	}
};

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 */
@@ -224,11 +419,41 @@ static struct platform_device *eva_devices[] __initdata = {
	&sh_eth_device,
};

static void __init eva_clock_init(void)
{
	struct clk *system	= clk_get(NULL, "system_clk");
	struct clk *xtal1	= clk_get(NULL, "extal1");
	struct clk *usb24s	= clk_get(NULL, "usb24s");

	if (IS_ERR(system)	||
	    IS_ERR(xtal1)	||
	    IS_ERR(usb24s)) {
		pr_err("armadillo800eva board clock init failed\n");
		goto clock_error;
	}

	/* armadillo 800 eva extal1 is 24MHz */
	clk_set_rate(xtal1, 24000000);

	/* usb24s use extal1 (= system) clock (= 24MHz) */
	clk_set_parent(usb24s, system);

clock_error:
	if (!IS_ERR(system))
		clk_put(system);
	if (!IS_ERR(xtal1))
		clk_put(xtal1);
	if (!IS_ERR(usb24s))
		clk_put(usb24s);
}

/*
 * board init
 */
static void __init eva_init(void)
{
	eva_clock_init();

	r8a7740_pinmux_init();

	/* SCIFA1 */
@@ -302,6 +527,18 @@ static void __init eva_init(void)
	gpio_request(GPIO_PORT18, NULL); /* PHY_RST */
	gpio_direction_output(GPIO_PORT18, 1);

	/* USB */
	gpio_request(GPIO_PORT159, NULL); /* USB_DEVICE_MODE */
	gpio_direction_input(GPIO_PORT159);

	if (gpio_get_value(GPIO_PORT159)) {
		/* USB Host */
	} else {
		/* USB Func */
		gpio_request(GPIO_FN_VBUS, NULL);
		platform_device_register(&usbhsf_device);
	}

	/*
	 * CAUTION
	 *
@@ -326,17 +563,7 @@ static void __init eva_init(void)

static void __init eva_earlytimer_init(void)
{
	struct clk *xtal1;

	r8a7740_clock_init(MD_CK0 | MD_CK2);

	xtal1 = clk_get(NULL, "extal1");
	if (!IS_ERR(xtal1)) {
		/* armadillo 800 eva extal1 is 24MHz */
		clk_set_rate(xtal1, 24000000);
		clk_put(xtal1);
	}

	shmobile_earlytimer_init();
}