From ba207d46b7857f6cae1368f0e62e611eb773505c Mon Sep 17 00:00:00 2001
From: Radek Polak <psonek2@seznam.cz>
Date: Fri, 7 Jan 2011 15:59:08 +0100
Subject: [PATCH 56/69] gta02: add support for usb host mode

adds support for usb host mode and adds usb host pm sysfs node. I just
copied and modified how modem is powered up and backported changes in
ohci-s3c2410.c. No idea if this is the right approach, but it works -
my usb keyboard is now working after:

echo 1 > /sys/devices/platform/s3c2440-i2c/i2c-0/0-0073/pcf50633-gpio/reg-fixed-voltage.2/gta02-pm-usbhost.0/power_on
echo host > /sys/devices/platform/s3c2410-ohci/usb_mode

Signed-off-by: Martin Jansa <Martin.Jansa@gmail.com>
---
 arch/arm/mach-s3c2440/Makefile           |    1 +
 arch/arm/mach-s3c2440/gta02-pm-usbhost.c |  174 ++++++++++++++++++++++++++++++
 arch/arm/mach-s3c2440/mach-gta02.c       |   48 ++++++++-
 drivers/usb/host/ohci-s3c2410.c          |   48 ++++++++
 4 files changed, 270 insertions(+), 1 deletions(-)
 create mode 100644 arch/arm/mach-s3c2440/gta02-pm-usbhost.c

diff --git a/arch/arm/mach-s3c2440/Makefile b/arch/arm/mach-s3c2440/Makefile
index 035d116..16d9855 100644
--- a/arch/arm/mach-s3c2440/Makefile
+++ b/arch/arm/mach-s3c2440/Makefile
@@ -38,6 +38,7 @@ obj-$(CONFIG_MACH_NEO1973_GTA02) += mach-gta02.o \
 	gta02-pm-bt.o \
 	gta02-pm-gps.o \
 	gta02-pm-gsm.o \
+	gta02-pm-usbhost.o \
 	gta02-pm-wlan.o \
 	gta02-fiq.o \
 	gta02-hdq.o \
diff --git a/arch/arm/mach-s3c2440/gta02-pm-usbhost.c b/arch/arm/mach-s3c2440/gta02-pm-usbhost.c
new file mode 100644
index 0000000..233340a
--- /dev/null
+++ b/arch/arm/mach-s3c2440/gta02-pm-usbhost.c
@@ -0,0 +1,174 @@
+/*
+ * USBHOST Management code for the Openmoko Freerunner GSM Phone
+ *
+ * (C) 2007 by Openmoko Inc.
+ * Author: Harald Welte <laforge@openmoko.org>
+ * All rights reserved.
+ *
+ * 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/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/console.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/regulator/consumer.h>
+
+#include <mach/gpio.h>
+#include <asm/mach-types.h>
+
+#include <mach/hardware.h>
+
+#include <mach/gta02.h>
+#include <mach/regs-gpio.h>
+#include <mach/regs-gpioj.h>
+
+static struct regulator *gta02_usbhost_regulator;
+
+static ssize_t usbhost_read(struct device *dev, struct device_attribute *attr,
+			char *buf)
+{
+	if (!strcmp(attr->attr.name, "power_on")) {
+		if (regulator_is_enabled(gta02_usbhost_regulator))
+			goto out_1;
+	}
+
+	return strlcpy(buf, "0\n", 3);
+out_1:
+	return strlcpy(buf, "1\n", 3);
+}
+
+static void usbhost_on_off(struct device *dev, int on)
+{
+
+	on = !!on;
+
+	if (on == regulator_is_enabled(gta02_usbhost_regulator))
+		return;
+
+	if (!on) {
+		regulator_disable(gta02_usbhost_regulator);
+		return;
+	}
+
+	regulator_enable(gta02_usbhost_regulator);
+}
+
+static ssize_t usbhost_write(struct device *dev, struct device_attribute *attr,
+			 const char *buf, size_t count)
+{
+	unsigned long on = simple_strtoul(buf, NULL, 10);
+
+	if (!strcmp(attr->attr.name, "power_on")) {
+		usbhost_on_off(dev, on);
+
+		return count;
+	}
+
+	return count;
+}
+
+static DEVICE_ATTR(power_on, 0644, usbhost_read, usbhost_write);
+
+#ifdef CONFIG_PM
+
+static int gta02_usbhost_suspend(struct device *dev)
+{
+	return 0;
+}
+
+static int gta02_usbhost_suspend_late(struct device *dev)
+{
+	return 0;
+}
+
+static int gta02_usbhost_resume(struct device *dev)
+{
+	return 0;
+}
+
+static struct dev_pm_ops gta02_usbhost_pm_ops = {
+	.suspend	= gta02_usbhost_suspend,
+	.suspend_noirq	= gta02_usbhost_suspend_late,
+	.resume		= gta02_usbhost_resume,
+};
+
+#define GTA02_USBHOST_PM_OPS (&gta02_usbhost_pm_ops)
+
+#else
+#define GTA02_USBHOST_PM_OPS NULL
+#endif /* CONFIG_PM */
+
+static struct attribute *gta02_usbhost_sysfs_entries[] = {
+	&dev_attr_power_on.attr,
+	NULL
+};
+
+static struct attribute_group gta02_usbhost_attr_group = {
+	.name	= NULL,
+	.attrs	= gta02_usbhost_sysfs_entries,
+};
+
+static int __init gta02_usbhost_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	gta02_usbhost_regulator = regulator_get_exclusive(&pdev->dev, "USBHOST");
+
+	if (IS_ERR(gta02_usbhost_regulator)) {
+		ret = PTR_ERR(gta02_usbhost_regulator);
+		dev_err(&pdev->dev, "Failed to get regulator: %d\n", ret);
+		return ret;
+	}
+
+	ret = sysfs_create_group(&pdev->dev.kobj, &gta02_usbhost_attr_group);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to create sysfs entries: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int gta02_usbhost_remove(struct platform_device *pdev)
+{
+	usbhost_on_off(&pdev->dev, 0);
+
+	sysfs_remove_group(&pdev->dev.kobj, &gta02_usbhost_attr_group);
+	regulator_put(gta02_usbhost_regulator);
+
+	return 0;
+}
+
+static struct platform_driver gta02_usbhost_driver = {
+	.probe		= gta02_usbhost_probe,
+	.remove		= gta02_usbhost_remove,
+	.driver		= {
+		.name	= "gta02-pm-usbhost",
+		.pm	= GTA02_USBHOST_PM_OPS,
+	},
+};
+
+static int __devinit gta02_usbhost_init(void)
+{
+	return platform_driver_register(&gta02_usbhost_driver);
+}
+module_init(gta02_usbhost_init);
+
+static void gta02_usbhost_exit(void)
+{
+	platform_driver_unregister(&gta02_usbhost_driver);
+}
+module_exit(gta02_usbhost_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Harald Welte <laforge@openmoko.org>");
+MODULE_DESCRIPTION("Openmoko Freerunner USBHOST Power Management");
diff --git a/arch/arm/mach-s3c2440/mach-gta02.c b/arch/arm/mach-s3c2440/mach-gta02.c
index d30d6ee..8b76bbf 100644
--- a/arch/arm/mach-s3c2440/mach-gta02.c
+++ b/arch/arm/mach-s3c2440/mach-gta02.c
@@ -181,6 +181,10 @@ static struct platform_device gta02_pm_gsm_dev = {
 	.name = "gta02-pm-gsm",
 };
 
+static struct platform_device gta02_pm_usbhost_dev = {
+	.name = "gta02-pm-usbhost",
+};
+
 static struct platform_device gta02_pm_wlan_dev = {
 	.name = "gta02-pm-wlan",
 };
@@ -190,6 +194,11 @@ static struct regulator_consumer_supply gsm_supply_consumer = {
 	.supply = "GSM",
 };
 
+static struct regulator_consumer_supply usbhost_supply_consumer = {
+	.dev = &gta02_pm_usbhost_dev.dev,
+	.supply = "USBHOST",
+};
+
 static struct regulator_init_data gsm_supply_init_data = {
 	.constraints = {
 		.min_uV = 3700000,
@@ -201,6 +210,17 @@ static struct regulator_init_data gsm_supply_init_data = {
 	.consumer_supplies = &gsm_supply_consumer,
 };
 
+static struct regulator_init_data usbhost_supply_init_data = {
+	.constraints = {
+		.min_uV = 3700000,
+		.max_uV = 3700000,
+		.valid_modes_mask = REGULATOR_MODE_NORMAL,
+		.valid_ops_mask = REGULATOR_CHANGE_STATUS,
+	},
+	.num_consumer_supplies = 1,
+	.consumer_supplies = &usbhost_supply_consumer,
+};
+
 static struct fixed_voltage_config gsm_supply_config = {
 	.supply_name = "GSM",
 	.microvolts = 3700000,
@@ -209,6 +229,14 @@ static struct fixed_voltage_config gsm_supply_config = {
 	.init_data = &gsm_supply_init_data,
 };
 
+static struct fixed_voltage_config usbhost_supply_config = {
+	.supply_name = "USBHOST",
+	.microvolts = 3700000,
+	.gpio = GTA02_GPIO_PCF(PCF50633_GPO),
+	.enable_high = 1,
+	.init_data = &usbhost_supply_init_data,
+};
+
 static struct platform_device gta02_gsm_supply_device = {
 	.name = "reg-fixed-voltage",
 	.id = 1,
@@ -217,6 +245,14 @@ static struct platform_device gta02_gsm_supply_device = {
 	},
 };
 
+static struct platform_device gta02_usbhost_supply_device = {
+	.name = "reg-fixed-voltage",
+	.id = 2,
+	.dev = {
+		.platform_data = &usbhost_supply_config,
+	},
+};
+
 /*
  * we crank down SD Card clock dynamically when GPS is powered
  */
@@ -1104,12 +1140,17 @@ static struct platform_device *gta02_glamo_gpio_children[] = {
 
 static struct platform_device *gta02_pcf50633_gpio_children[] = {
 	&gta02_gsm_supply_device,
+	&gta02_usbhost_supply_device,
 };
 
 static struct platform_device *gta02_gsm_supply_children[] = {
 	&gta02_pm_gsm_dev,
 };
 
+static struct platform_device* gta02_usbhost_supply_children[] = {
+	&gta02_pm_usbhost_dev,
+};
+
 static struct platform_device *gta02_hdq_children[] = {
 	&bq27000_battery_device,
 };
@@ -1123,7 +1164,7 @@ static struct gta02_device_children gta02_device_children[] = {
 	},
 	{
 		.dev_name = "pcf50633-gpio",
-		.num_children = 1,
+		.num_children = 2,
 		.children = gta02_pcf50633_gpio_children,
 	},
 	{
@@ -1132,6 +1173,11 @@ static struct gta02_device_children gta02_device_children[] = {
 		.children = gta02_gsm_supply_children,
 	},
 	{
+		.dev_name = "reg-fixed-voltage.2",
+		.num_children = 1,
+		.children = gta02_usbhost_supply_children,
+	},
+	{
 		.dev_name = "spi2.0",
 		.probed_callback = gta02_jbt6k74_probe_completed,
 	},
diff --git a/drivers/usb/host/ohci-s3c2410.c b/drivers/usb/host/ohci-s3c2410.c
index a68af2d..02bd7b0 100644
--- a/drivers/usb/host/ohci-s3c2410.c
+++ b/drivers/usb/host/ohci-s3c2410.c
@@ -22,6 +22,10 @@
 #include <linux/platform_device.h>
 #include <linux/clk.h>
 #include <plat/usb-control.h>
+#include <mach/hardware.h>
+#include <mach/gpio-fns.h>
+#include <mach/regs-gpio.h>
+#include <mach/gta02.h>
 
 #define valid_port(idx) ((idx) == 1 || (idx) == 2)
 
@@ -306,6 +310,42 @@ static void s3c2410_hcd_oc(struct s3c2410_hcd_info *info, int port_oc)
 	local_irq_restore(flags);
 }
 
+/* switching of USB pads */
+static ssize_t show_usb_mode(struct device *dev, struct device_attribute *attr,
+			     char *buf)
+{
+	if (__raw_readl(S3C24XX_MISCCR) & S3C2410_MISCCR_USBHOST)
+		return sprintf(buf, "host\n");
+
+	return sprintf(buf, "device\n");
+}
+
+static ssize_t set_usb_mode(struct device *dev, struct device_attribute *attr,
+			    const char *buf, size_t count)
+{
+	if (!strncmp(buf, "host", 4)) {
+		printk("s3c2410: changing usb to host\n");
+		s3c2410_modify_misccr(S3C2410_MISCCR_USBHOST,
+				      S3C2410_MISCCR_USBHOST);
+		/* FIXME:
+		 * - call machine-specific disable-pullup function i
+		 * - enable +Vbus (if hardware supports it)
+		 */
+		s3c2410_gpio_setpin(GTA02_GPIO_USB_PULLUP, 0);
+	} else if (!strncmp(buf, "device", 6)) {
+		printk("s3c2410: changing usb to device\n");
+		s3c2410_modify_misccr(S3C2410_MISCCR_USBHOST, 0);
+		s3c2410_gpio_setpin(GTA02_GPIO_USB_PULLUP, 1);
+	} else {
+		printk("s3c2410: unknown mode\n");
+		return -EINVAL;
+	}
+
+	return count;
+}
+
+static DEVICE_ATTR(usb_mode, S_IRUGO | S_IWUSR, show_usb_mode, set_usb_mode);
+
 /* may be called without controller electrically present */
 /* may be called with controller, bus, and devices active */
 
@@ -323,6 +363,7 @@ static void s3c2410_hcd_oc(struct s3c2410_hcd_info *info, int port_oc)
 static void
 usb_hcd_s3c2410_remove (struct usb_hcd *hcd, struct platform_device *dev)
 {
+	device_remove_file(&dev->dev, &dev_attr_usb_mode);
 	usb_remove_hcd(hcd);
 	s3c2410_stop_hc(dev);
 	iounmap(hcd->regs);
@@ -390,8 +431,15 @@ static int usb_hcd_s3c2410_probe (const struct hc_driver *driver,
 	if (retval != 0)
 		goto err_ioremap;
 
+	retval = device_create_file(&dev->dev, &dev_attr_usb_mode);
+	if (retval != 0)
+		goto err_hcd;
+
 	return 0;
 
+ err_hcd:
+	usb_remove_hcd(hcd);
+
  err_ioremap:
 	s3c2410_stop_hc(dev);
 	iounmap(hcd->regs);
-- 
1.7.2.5

