Merge branch 'samsung/ohci' into next/drivers

* samsung/ohci:
  ARM: EXYNOS: Add USB OHCI support to ORIGEN board
  USB: Add Samsung Exynos OHCI diver
  ARM: EXYNOS: Add USB OHCI support to SMDKV310 board
  ARM: EXYNOS: Add USB OHCI device
diff --git a/arch/arm/mach-exynos/Kconfig b/arch/arm/mach-exynos/Kconfig
index 724ec0f..5ca0bdd 100644
--- a/arch/arm/mach-exynos/Kconfig
+++ b/arch/arm/mach-exynos/Kconfig
@@ -82,6 +82,11 @@
 	help
 	  Compile in platform device definitions for DWMCI
 
+config EXYNOS4_DEV_USB_OHCI
+	bool
+	help
+	  Compile in platform device definition for USB OHCI
+
 config EXYNOS4_SETUP_I2C1
 	bool
 	help
@@ -179,6 +184,7 @@
 	select SAMSUNG_DEV_KEYPAD
 	select EXYNOS4_DEV_PD
 	select SAMSUNG_DEV_PWM
+	select EXYNOS4_DEV_USB_OHCI
 	select EXYNOS4_DEV_SYSMMU
 	select EXYNOS4_SETUP_FIMD0
 	select EXYNOS4_SETUP_I2C1
@@ -288,6 +294,7 @@
 	select SAMSUNG_DEV_BACKLIGHT
 	select SAMSUNG_DEV_PWM
 	select EXYNOS4_DEV_PD
+	select EXYNOS4_DEV_USB_OHCI
 	select EXYNOS4_SETUP_FIMD0
 	select EXYNOS4_SETUP_SDHCI
 	select EXYNOS4_SETUP_USB_PHY
diff --git a/arch/arm/mach-exynos/Makefile b/arch/arm/mach-exynos/Makefile
index 59069a3..f5f3b79 100644
--- a/arch/arm/mach-exynos/Makefile
+++ b/arch/arm/mach-exynos/Makefile
@@ -44,6 +44,7 @@
 obj-$(CONFIG_EXYNOS4_DEV_PD)		+= dev-pd.o
 obj-$(CONFIG_EXYNOS4_DEV_SYSMMU)	+= dev-sysmmu.o
 obj-$(CONFIG_EXYNOS4_DEV_DWMCI)		+= dev-dwmci.o
+obj-$(CONFIG_EXYNOS4_DEV_USB_OHCI)	+= dev-ohci.o
 
 obj-$(CONFIG_EXYNOS4_SETUP_FIMC)	+= setup-fimc.o
 obj-$(CONFIG_EXYNOS4_SETUP_FIMD0)	+= setup-fimd0.o
diff --git a/arch/arm/mach-exynos/dev-ohci.c b/arch/arm/mach-exynos/dev-ohci.c
new file mode 100644
index 0000000..b8e7530
--- /dev/null
+++ b/arch/arm/mach-exynos/dev-ohci.c
@@ -0,0 +1,52 @@
+/* linux/arch/arm/mach-exynos/dev-ohci.c
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ *		http://www.samsung.com
+ *
+ * EXYNOS - OHCI support
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/platform_device.h>
+
+#include <mach/irqs.h>
+#include <mach/map.h>
+#include <mach/ohci.h>
+
+#include <plat/devs.h>
+#include <plat/usb-phy.h>
+
+static struct resource exynos4_ohci_resource[] = {
+	[0] = DEFINE_RES_MEM(EXYNOS4_PA_OHCI, SZ_256),
+	[1] = DEFINE_RES_IRQ(IRQ_USB_HOST),
+};
+
+static u64 exynos4_ohci_dma_mask = DMA_BIT_MASK(32);
+
+struct platform_device exynos4_device_ohci = {
+	.name		= "exynos-ohci",
+	.id		= -1,
+	.num_resources	= ARRAY_SIZE(exynos4_ohci_resource),
+	.resource	= exynos4_ohci_resource,
+	.dev		= {
+		.dma_mask		= &exynos4_ohci_dma_mask,
+		.coherent_dma_mask	= DMA_BIT_MASK(32),
+	}
+};
+
+void __init exynos4_ohci_set_platdata(struct exynos4_ohci_platdata *pd)
+{
+	struct exynos4_ohci_platdata *npd;
+
+	npd = s3c_set_platdata(pd, sizeof(struct exynos4_ohci_platdata),
+			&exynos4_device_ohci);
+
+	if (!npd->phy_init)
+		npd->phy_init = s5p_usb_phy_init;
+	if (!npd->phy_exit)
+		npd->phy_exit = s5p_usb_phy_exit;
+}
diff --git a/arch/arm/mach-exynos/include/mach/map.h b/arch/arm/mach-exynos/include/mach/map.h
index 058541d..01e1cf3 100644
--- a/arch/arm/mach-exynos/include/mach/map.h
+++ b/arch/arm/mach-exynos/include/mach/map.h
@@ -107,6 +107,7 @@
 #define EXYNOS4_PA_SROMC		0x12570000
 
 #define EXYNOS4_PA_EHCI			0x12580000
+#define EXYNOS4_PA_OHCI			0x12590000
 #define EXYNOS4_PA_HSPHY		0x125B0000
 #define EXYNOS4_PA_MFC			0x13400000
 
diff --git a/arch/arm/mach-exynos/include/mach/ohci.h b/arch/arm/mach-exynos/include/mach/ohci.h
new file mode 100644
index 0000000..c256c59
--- /dev/null
+++ b/arch/arm/mach-exynos/include/mach/ohci.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2011 Samsung Electronics Co.Ltd
+ *		http://www.samsung.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;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+
+#ifndef __MACH_EXYNOS_OHCI_H
+#define __MACH_EXYNOS_OHCI_H
+
+struct exynos4_ohci_platdata {
+	int (*phy_init)(struct platform_device *pdev, int type);
+	int (*phy_exit)(struct platform_device *pdev, int type);
+};
+
+extern void exynos4_ohci_set_platdata(struct exynos4_ohci_platdata *pd);
+
+#endif /* __MACH_EXYNOS_OHCI_H */
diff --git a/arch/arm/mach-exynos/mach-origen.c b/arch/arm/mach-exynos/mach-origen.c
index f80b563..b805e59 100644
--- a/arch/arm/mach-exynos/mach-origen.c
+++ b/arch/arm/mach-exynos/mach-origen.c
@@ -41,6 +41,7 @@
 #include <plat/fb.h>
 #include <plat/mfc.h>
 
+#include <mach/ohci.h>
 #include <mach/map.h>
 
 /* Following are default values for UCON, ULCON and UFCON UART registers */
@@ -483,6 +484,16 @@
 	s5p_ehci_set_platdata(pdata);
 }
 
+/* USB OHCI */
+static struct exynos4_ohci_platdata origen_ohci_pdata;
+
+static void __init origen_ohci_init(void)
+{
+	struct exynos4_ohci_platdata *pdata = &origen_ohci_pdata;
+
+	exynos4_ohci_set_platdata(pdata);
+}
+
 static struct gpio_keys_button origen_gpio_keys_table[] = {
 	{
 		.code			= KEY_MENU,
@@ -606,6 +617,7 @@
 	&s5p_device_mfc_l,
 	&s5p_device_mfc_r,
 	&s5p_device_mixer,
+	&exynos4_device_ohci,
 	&exynos4_device_pd[PD_LCD0],
 	&exynos4_device_pd[PD_TV],
 	&exynos4_device_pd[PD_G3D],
@@ -670,6 +682,7 @@
 	s3c_sdhci0_set_platdata(&origen_hsmmc0_pdata);
 
 	origen_ehci_init();
+	origen_ohci_init();
 	clk_xusbxti.rate = 24000000;
 
 	s5p_tv_setup();
diff --git a/arch/arm/mach-exynos/mach-smdkv310.c b/arch/arm/mach-exynos/mach-smdkv310.c
index cec2afa..25a5a40 100644
--- a/arch/arm/mach-exynos/mach-smdkv310.c
+++ b/arch/arm/mach-exynos/mach-smdkv310.c
@@ -42,6 +42,7 @@
 #include <plat/clock.h>
 
 #include <mach/map.h>
+#include <mach/ohci.h>
 
 /* Following are default values for UCON, ULCON and UFCON UART registers */
 #define SMDKV310_UCON_DEFAULT	(S3C2410_UCON_TXILEVEL |	\
@@ -245,6 +246,16 @@
 	s5p_ehci_set_platdata(pdata);
 }
 
+/* USB OHCI */
+static struct exynos4_ohci_platdata smdkv310_ohci_pdata;
+
+static void __init smdkv310_ohci_init(void)
+{
+	struct exynos4_ohci_platdata *pdata = &smdkv310_ohci_pdata;
+
+	exynos4_ohci_set_platdata(pdata);
+}
+
 static struct platform_device *smdkv310_devices[] __initdata = {
 	&s3c_device_hsmmc0,
 	&s3c_device_hsmmc1,
@@ -261,6 +272,7 @@
 	&s5p_device_fimc3,
 	&exynos4_device_ac97,
 	&exynos4_device_i2s0,
+	&exynos4_device_ohci,
 	&samsung_device_keypad,
 	&s5p_device_mfc,
 	&s5p_device_mfc_l,
@@ -363,6 +375,7 @@
 	s5p_fimd0_set_platdata(&smdkv310_lcd0_pdata);
 
 	smdkv310_ehci_init();
+	smdkv310_ohci_init();
 	clk_xusbxti.rate = 24000000;
 
 	platform_add_devices(smdkv310_devices, ARRAY_SIZE(smdkv310_devices));
diff --git a/arch/arm/mach-exynos/setup-usb-phy.c b/arch/arm/mach-exynos/setup-usb-phy.c
index 39aca04..41743d2 100644
--- a/arch/arm/mach-exynos/setup-usb-phy.c
+++ b/arch/arm/mach-exynos/setup-usb-phy.c
@@ -19,6 +19,13 @@
 #include <plat/cpu.h>
 #include <plat/usb-phy.h>
 
+static atomic_t host_usage;
+
+static int exynos4_usb_host_phy_is_on(void)
+{
+	return (readl(EXYNOS4_PHYPWR) & PHY1_STD_ANALOG_POWERDOWN) ? 0 : 1;
+}
+
 static int exynos4_usb_phy1_init(struct platform_device *pdev)
 {
 	struct clk *otg_clk;
@@ -27,6 +34,8 @@
 	u32 rstcon;
 	int err;
 
+	atomic_inc(&host_usage);
+
 	otg_clk = clk_get(&pdev->dev, "otg");
 	if (IS_ERR(otg_clk)) {
 		dev_err(&pdev->dev, "Failed to get otg clock\n");
@@ -39,6 +48,9 @@
 		return err;
 	}
 
+	if (exynos4_usb_host_phy_is_on())
+		return 0;
+
 	writel(readl(S5P_USBHOST_PHY_CONTROL) | S5P_USBHOST_PHY_ENABLE,
 			S5P_USBHOST_PHY_CONTROL);
 
@@ -95,6 +107,9 @@
 	struct clk *otg_clk;
 	int err;
 
+	if (atomic_dec_return(&host_usage) > 0)
+		return 0;
+
 	otg_clk = clk_get(&pdev->dev, "otg");
 	if (IS_ERR(otg_clk)) {
 		dev_err(&pdev->dev, "Failed to get otg clock\n");
diff --git a/arch/arm/plat-samsung/include/plat/devs.h b/arch/arm/plat-samsung/include/plat/devs.h
index ab633c9..a2ff27e 100644
--- a/arch/arm/plat-samsung/include/plat/devs.h
+++ b/arch/arm/plat-samsung/include/plat/devs.h
@@ -129,6 +129,7 @@
 extern struct platform_device exynos4_device_i2s0;
 extern struct platform_device exynos4_device_i2s1;
 extern struct platform_device exynos4_device_i2s2;
+extern struct platform_device exynos4_device_ohci;
 extern struct platform_device exynos4_device_pcm0;
 extern struct platform_device exynos4_device_pcm1;
 extern struct platform_device exynos4_device_pcm2;
diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
index 791f11b..75823a1 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -48,6 +48,7 @@
 	default y if ARCH_DAVINCI_DA8XX
 	default y if ARCH_CNS3XXX
 	default y if PLAT_SPEAR
+	default y if ARCH_EXYNOS
 	# PPC:
 	default y if STB03xxx
 	default y if PPC_MPC52xx
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 060e0e2..eea85dc 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -371,6 +371,12 @@
 	  Enables support for the on-chip OHCI controller on the SuperH.
 	  If you use the PCI OHCI controller, this option is not necessary.
 
+config USB_OHCI_EXYNOS
+	boolean "OHCI support for Samsung EXYNOS SoC Series"
+	depends on USB_OHCI_HCD && ARCH_EXYNOS
+	help
+	 Enable support for the Samsung Exynos SOC's on-chip OHCI controller.
+
 config USB_CNS3XXX_OHCI
 	bool "Cavium CNS3XXX OHCI Module"
 	depends on USB_OHCI_HCD && ARCH_CNS3XXX
diff --git a/drivers/usb/host/ohci-exynos.c b/drivers/usb/host/ohci-exynos.c
new file mode 100644
index 0000000..55aa35a
--- /dev/null
+++ b/drivers/usb/host/ohci-exynos.c
@@ -0,0 +1,274 @@
+/*
+ * SAMSUNG EXYNOS USB HOST OHCI Controller
+ *
+ * Copyright (C) 2011 Samsung Electronics Co.Ltd
+ * Author: Jingoo Han <jg1.han@samsung.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;  either version 2 of the  License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <mach/ohci.h>
+#include <plat/usb-phy.h>
+
+struct exynos_ohci_hcd {
+	struct device *dev;
+	struct usb_hcd *hcd;
+	struct clk *clk;
+};
+
+static int ohci_exynos_start(struct usb_hcd *hcd)
+{
+	struct ohci_hcd *ohci = hcd_to_ohci(hcd);
+	int ret;
+
+	ohci_dbg(ohci, "ohci_exynos_start, ohci:%p", ohci);
+
+	ret = ohci_init(ohci);
+	if (ret < 0)
+		return ret;
+
+	ret = ohci_run(ohci);
+	if (ret < 0) {
+		err("can't start %s", hcd->self.bus_name);
+		ohci_stop(hcd);
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct hc_driver exynos_ohci_hc_driver = {
+	.description		= hcd_name,
+	.product_desc		= "EXYNOS OHCI Host Controller",
+	.hcd_priv_size		= sizeof(struct ohci_hcd),
+
+	.irq			= ohci_irq,
+	.flags			= HCD_MEMORY|HCD_USB11,
+
+	.start			= ohci_exynos_start,
+	.stop			= ohci_stop,
+	.shutdown		= ohci_shutdown,
+
+	.get_frame_number	= ohci_get_frame,
+
+	.urb_enqueue		= ohci_urb_enqueue,
+	.urb_dequeue		= ohci_urb_dequeue,
+	.endpoint_disable	= ohci_endpoint_disable,
+
+	.hub_status_data	= ohci_hub_status_data,
+	.hub_control		= ohci_hub_control,
+#ifdef	CONFIG_PM
+	.bus_suspend		= ohci_bus_suspend,
+	.bus_resume		= ohci_bus_resume,
+#endif
+	.start_port_reset	= ohci_start_port_reset,
+};
+
+static int __devinit exynos_ohci_probe(struct platform_device *pdev)
+{
+	struct exynos4_ohci_platdata *pdata;
+	struct exynos_ohci_hcd *exynos_ohci;
+	struct usb_hcd *hcd;
+	struct ohci_hcd *ohci;
+	struct resource *res;
+	int irq;
+	int err;
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_err(&pdev->dev, "No platform data defined\n");
+		return -EINVAL;
+	}
+
+	exynos_ohci = kzalloc(sizeof(struct exynos_ohci_hcd), GFP_KERNEL);
+	if (!exynos_ohci)
+		return -ENOMEM;
+
+	exynos_ohci->dev = &pdev->dev;
+
+	hcd = usb_create_hcd(&exynos_ohci_hc_driver, &pdev->dev,
+					dev_name(&pdev->dev));
+	if (!hcd) {
+		dev_err(&pdev->dev, "Unable to create HCD\n");
+		err = -ENOMEM;
+		goto fail_hcd;
+	}
+
+	exynos_ohci->hcd = hcd;
+	exynos_ohci->clk = clk_get(&pdev->dev, "usbhost");
+
+	if (IS_ERR(exynos_ohci->clk)) {
+		dev_err(&pdev->dev, "Failed to get usbhost clock\n");
+		err = PTR_ERR(exynos_ohci->clk);
+		goto fail_clk;
+	}
+
+	err = clk_enable(exynos_ohci->clk);
+	if (err)
+		goto fail_clken;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "Failed to get I/O memory\n");
+		err = -ENXIO;
+		goto fail_io;
+	}
+
+	hcd->rsrc_start = res->start;
+	hcd->rsrc_len = resource_size(res);
+	hcd->regs = ioremap(res->start, resource_size(res));
+	if (!hcd->regs) {
+		dev_err(&pdev->dev, "Failed to remap I/O memory\n");
+		err = -ENOMEM;
+		goto fail_io;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (!irq) {
+		dev_err(&pdev->dev, "Failed to get IRQ\n");
+		err = -ENODEV;
+		goto fail;
+	}
+
+	if (pdata->phy_init)
+		pdata->phy_init(pdev, S5P_USB_PHY_HOST);
+
+	ohci = hcd_to_ohci(hcd);
+	ohci_hcd_init(ohci);
+
+	err = usb_add_hcd(hcd, irq, IRQF_SHARED);
+	if (err) {
+		dev_err(&pdev->dev, "Failed to add USB HCD\n");
+		goto fail;
+	}
+
+	platform_set_drvdata(pdev, exynos_ohci);
+
+	return 0;
+
+fail:
+	iounmap(hcd->regs);
+fail_io:
+	clk_disable(exynos_ohci->clk);
+fail_clken:
+	clk_put(exynos_ohci->clk);
+fail_clk:
+	usb_put_hcd(hcd);
+fail_hcd:
+	kfree(exynos_ohci);
+	return err;
+}
+
+static int __devexit exynos_ohci_remove(struct platform_device *pdev)
+{
+	struct exynos4_ohci_platdata *pdata = pdev->dev.platform_data;
+	struct exynos_ohci_hcd *exynos_ohci = platform_get_drvdata(pdev);
+	struct usb_hcd *hcd = exynos_ohci->hcd;
+
+	usb_remove_hcd(hcd);
+
+	if (pdata && pdata->phy_exit)
+		pdata->phy_exit(pdev, S5P_USB_PHY_HOST);
+
+	iounmap(hcd->regs);
+
+	clk_disable(exynos_ohci->clk);
+	clk_put(exynos_ohci->clk);
+
+	usb_put_hcd(hcd);
+	kfree(exynos_ohci);
+
+	return 0;
+}
+
+static void exynos_ohci_shutdown(struct platform_device *pdev)
+{
+	struct exynos_ohci_hcd *exynos_ohci = platform_get_drvdata(pdev);
+	struct usb_hcd *hcd = exynos_ohci->hcd;
+
+	if (hcd->driver->shutdown)
+		hcd->driver->shutdown(hcd);
+}
+
+#ifdef CONFIG_PM
+static int exynos_ohci_suspend(struct device *dev)
+{
+	struct exynos_ohci_hcd *exynos_ohci = dev_get_drvdata(dev);
+	struct usb_hcd *hcd = exynos_ohci->hcd;
+	struct ohci_hcd *ohci = hcd_to_ohci(hcd);
+	struct platform_device *pdev = to_platform_device(dev);
+	struct exynos4_ohci_platdata *pdata = pdev->dev.platform_data;
+	unsigned long flags;
+	int rc = 0;
+
+	/*
+	 * Root hub was already suspended. Disable irq emission and
+	 * mark HW unaccessible, bail out if RH has been resumed. Use
+	 * the spinlock to properly synchronize with possible pending
+	 * RH suspend or resume activity.
+	 *
+	 * This is still racy as hcd->state is manipulated outside of
+	 * any locks =P But that will be a different fix.
+	 */
+	spin_lock_irqsave(&ohci->lock, flags);
+	if (hcd->state != HC_STATE_SUSPENDED && hcd->state != HC_STATE_HALT) {
+		rc = -EINVAL;
+		goto fail;
+	}
+
+	clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+
+	if (pdata && pdata->phy_exit)
+		pdata->phy_exit(pdev, S5P_USB_PHY_HOST);
+fail:
+	spin_unlock_irqrestore(&ohci->lock, flags);
+
+	return rc;
+}
+
+static int exynos_ohci_resume(struct device *dev)
+{
+	struct exynos_ohci_hcd *exynos_ohci = dev_get_drvdata(dev);
+	struct usb_hcd *hcd = exynos_ohci->hcd;
+	struct platform_device *pdev = to_platform_device(dev);
+	struct exynos4_ohci_platdata *pdata = pdev->dev.platform_data;
+
+	if (pdata && pdata->phy_init)
+		pdata->phy_init(pdev, S5P_USB_PHY_HOST);
+
+	/* Mark hardware accessible again as we are out of D3 state by now */
+	set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+
+	ohci_finish_controller_resume(hcd);
+
+	return 0;
+}
+#else
+#define exynos_ohci_suspend	NULL
+#define exynos_ohci_resume	NULL
+#endif
+
+static const struct dev_pm_ops exynos_ohci_pm_ops = {
+	.suspend	= exynos_ohci_suspend,
+	.resume		= exynos_ohci_resume,
+};
+
+static struct platform_driver exynos_ohci_driver = {
+	.probe		= exynos_ohci_probe,
+	.remove		= __devexit_p(exynos_ohci_remove),
+	.shutdown	= exynos_ohci_shutdown,
+	.driver = {
+		.name	= "exynos-ohci",
+		.owner	= THIS_MODULE,
+		.pm	= &exynos_ohci_pm_ops,
+	}
+};
+
+MODULE_ALIAS("platform:exynos-ohci");
+MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c
index b263919..a1006ff 100644
--- a/drivers/usb/host/ohci-hcd.c
+++ b/drivers/usb/host/ohci-hcd.c
@@ -1005,6 +1005,11 @@
 #define PLATFORM_DRIVER		ohci_hcd_s3c2410_driver
 #endif
 
+#ifdef CONFIG_USB_OHCI_EXYNOS
+#include "ohci-exynos.c"
+#define PLATFORM_DRIVER		exynos_ohci_driver
+#endif
+
 #ifdef CONFIG_USB_OHCI_HCD_OMAP1
 #include "ohci-omap.c"
 #define OMAP1_PLATFORM_DRIVER	ohci_hcd_omap_driver