summaryrefslogtreecommitdiff
path: root/arch/arm/mach-s3c2443/cc9m2443js-usb.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-s3c2443/cc9m2443js-usb.c')
-rw-r--r--arch/arm/mach-s3c2443/cc9m2443js-usb.c195
1 files changed, 195 insertions, 0 deletions
diff --git a/arch/arm/mach-s3c2443/cc9m2443js-usb.c b/arch/arm/mach-s3c2443/cc9m2443js-usb.c
new file mode 100644
index 000000000000..bcd240e7dc5d
--- /dev/null
+++ b/arch/arm/mach-s3c2443/cc9m2443js-usb.c
@@ -0,0 +1,195 @@
+/* -*- linux-c -*-
+ *
+ * linux/arch/arm/mach-s3c2443/cc9m2443js-usb.c
+ *
+ * Copyright (c) 2004,2005 Simtec Electronics
+ * Ben Dooks <ben@simtec.co.uk>
+ *
+ * http://www.simtec.co.uk/products/EB2410ITX/
+ *
+ * Based on the Simtec platform code
+ *
+ * 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/kernel.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/list.h>
+#include <linux/timer.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+
+#include <asm/mach/arch.h>
+#include <asm/mach/map.h>
+#include <asm/mach/irq.h>
+
+#include <mach/bast-map.h>
+#include <mach/bast-irq.h>
+#include <mach/usb-control.h>
+#include <mach/regs-gpio.h>
+#include <mach/gpio.h>
+
+#include <mach/hardware.h>
+#include <asm/irq.h>
+
+#include <plat/devs.h>
+
+#include "cc9m2443js-usb.h"
+
+/* control power and monitor over-current events on various Simtec
+ * designed boards.
+ */
+
+static unsigned int power_state[2];
+
+static void cc9m2443js_usb_timer_func(unsigned long _info)
+{
+ struct s3c2410_hcd_info *info;
+
+ info = (struct s3c2410_hcd_info *)_info;
+
+ s3c2410_usb_report_oc(info, 0);
+
+ /*
+ * IMPORTANT: Some power switches, like the MAX890, reports an overload
+ * when they are powered on. For avoiding endless overload reports it's
+ * required to wait for some time (> 400usecs.) before reenabling the
+ * interrupt line of the overload detection.
+ * (Luis Galdos)
+ */
+ mdelay(1);
+
+ enable_irq(info->oc_irq);
+}
+
+/*
+ * If the USB-PHY is used as second root hub port, then we must take care of its
+ * current state, otherwise only the first port is considered.
+ */
+#if defined(CONFIG_S3C2443_USB_PHY_PORT)
+# define CC9M2443JS_USB_PORT_ON(state) (state[0] && state[1])
+#else
+# define CC9M2443JS_USB_PORT_ON(state) (state[0])
+#endif /* CONFIG_S3C2443_USB_PHY_PORT */
+
+static void cc9m2443js_usb_powercontrol(struct s3c2410_hcd_info * info, int port, int to)
+{
+ pr_debug("USB power: port %d | to %d [%d %d]\n",
+ port, to, power_state[0], power_state[1]);
+
+ power_state[port] = to;
+
+ /* @XXX: Power control without a configured GPIO? */
+ if (!info->pw_gpio)
+ return;
+
+ if (CC9M2443JS_USB_PORT_ON(power_state))
+ s3c2443_gpio_setpin(info->pw_gpio, 1 ^ info->pw_gpio_inv);
+ else
+ s3c2443_gpio_setpin(info->pw_gpio, 0 ^ info->pw_gpio_inv);
+}
+
+static irqreturn_t cc9m2443js_usb_ocirq(int irq, void *_info)
+{
+ struct s3c2410_hcd_info *info = _info;
+
+ if (s3c2410_gpio_getpin(info->oc_gpio) == 0) {
+ s3c2410_usb_report_oc(info, 3);
+ pr_info("USB IRQ: Overcurrent Detected\n");
+ } else {
+
+ disable_irq(info->oc_irq);
+ pr_debug("USB IRQ: Scheduling timer for clearing OC\n");
+ mod_timer(&info->timer, jiffies + jiffies_to_msecs(100));
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * The S3C2443 doesn't have a dedicated interrupt for the OC detection.
+ * We use the external interrupt line connected to the GPG8
+ */
+static void cc9m2443js_usb_enableoc(struct s3c2410_hcd_info *info, int on)
+{
+ int ret;
+
+ pr_debug("Command to %s OC detection\n", on ? "enable" : "disable");
+
+ info->oc_irq = s3c2443_gpio_getirq(info->oc_gpio);
+ if (info->oc_irq < 0) {
+ printk(KERN_ERR "[ ERROR ] Couldn't get a GPIO interrupt\n");
+ return;
+ }
+
+ if (on) {
+ ret = request_irq(info->oc_irq, cc9m2443js_usb_ocirq,
+ IRQF_DISABLED | IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING,
+ "s3c2443 USB OC", info);
+ if (ret != 0)
+ printk(KERN_ERR "[ ERROR ] Failed to request usb oc irq\n");
+ } else
+ free_irq(info->oc_irq, info);
+}
+
+static struct s3c2410_hcd_info cc9m2443js_usb_info = {
+ .port[0] = {
+ .flags = S3C_HCDFLG_USED
+ },
+
+ /* Enable the second port if required */
+#if defined(CONFIG_S3C2443_USB_PHY_PORT)
+ .port[1] = {
+ .flags = S3C_HCDFLG_USED
+ },
+#endif /* CONFIG_S3C2443_USB_PHY_PORT */
+
+ .power_control = cc9m2443js_usb_powercontrol,
+ .enable_oc = cc9m2443js_usb_enableoc,
+
+ /* Over current GPIO */
+ .oc_gpio = S3C2410_GPG8,
+ .oc_gpio_cfg = S3C2410_GPG8_EINT16,
+
+ /* Power switch GPIO (inverted by the CC9M2443JS) */
+ .pw_gpio = S3C2410_GPA14,
+ .pw_gpio_cfg = S3C2410_GPA14_OUT,
+ .pw_gpio_inv = 1,
+};
+
+int __init cc9m2443js_usb_init(void)
+{
+ struct s3c2410_hcd_info *info = &cc9m2443js_usb_info;
+
+ s3c_device_usb.dev.platform_data = &cc9m2443js_usb_info;
+
+ /* Configure the GPIO and disable the pullups */
+ if (info->oc_gpio) {
+ s3c2443_gpio_cfgpin(info->oc_gpio, info->oc_gpio_cfg);
+ s3c2443_gpio_extpull(info->oc_gpio, 0);
+ s3c2443_gpio_set_udp(info->oc_gpio, 1);
+ }
+
+ if (info->pw_gpio) {
+ s3c2443_gpio_cfgpin(info->pw_gpio, info->pw_gpio_cfg);
+
+ /* At first power off the ports */
+ s3c2443_gpio_setpin(info->pw_gpio, 0 ^ info->pw_gpio_inv);
+ }
+
+ /* Init the internal timer */
+ init_timer(&info->timer);
+ info->timer.expires = 0;
+ info->timer.data = (unsigned long)info;
+ info->timer.function = cc9m2443js_usb_timer_func;
+ add_timer(&info->timer);
+
+ return 0;
+}