aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/misc/exynos-usb-switch.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/misc/exynos-usb-switch.c')
-rw-r--r--drivers/usb/misc/exynos-usb-switch.c642
1 files changed, 642 insertions, 0 deletions
diff --git a/drivers/usb/misc/exynos-usb-switch.c b/drivers/usb/misc/exynos-usb-switch.c
new file mode 100644
index 0000000..519c845
--- /dev/null
+++ b/drivers/usb/misc/exynos-usb-switch.c
@@ -0,0 +1,642 @@
+/*
+ * exynos-usb-switch.c - USB switch driver for Exynos
+ *
+ * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd.
+ * Yulgon Kim <yulgon.kim@samsung.com>
+ *
+ * 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/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+
+#include <plat/devs.h>
+#include <plat/ehci.h>
+#include <plat/usbgadget.h>
+#include <plat/usb-switch.h>
+
+#include <mach/regs-clock.h>
+
+#include "../gadget/s3c_udc.h"
+#include "../gadget/exynos_ss_udc.h"
+#include "exynos-usb-switch.h"
+
+#define DRIVER_DESC "Exynos USB Switch Driver"
+#define SWITCH_WAIT_TIME 500
+#define WAIT_TIMES 10
+
+static const char switch_name[] = "exynos_usb_switch";
+static struct exynos_usb_switch *our_switch;
+
+#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS)
+void exynos_usb_cable_connect(void)
+{
+ samsung_cable_check_status(1);
+}
+
+void exynos_usb_cable_disconnect(void)
+{
+ samsung_cable_check_status(0);
+}
+#endif
+
+static int is_host_detect(struct exynos_usb_switch *usb_switch)
+{
+ if (!usb_switch->gpio_host_detect)
+ return 0;
+ return !gpio_get_value(usb_switch->gpio_host_detect);
+}
+
+static int is_device_detect(struct exynos_usb_switch *usb_switch)
+{
+ if (!usb_switch->gpio_device_detect)
+ return 0;
+ return gpio_get_value(usb_switch->gpio_device_detect);
+}
+
+static int is_drd_host_detect(struct exynos_usb_switch *usb_switch)
+{
+ if (!usb_switch->gpio_drd_host_detect)
+ return 0;
+ return !gpio_get_value(usb_switch->gpio_drd_host_detect);
+}
+
+static int is_drd_device_detect(struct exynos_usb_switch *usb_switch)
+{
+ if (!usb_switch->gpio_drd_device_detect)
+ return 0;
+ return gpio_get_value(usb_switch->gpio_drd_device_detect);
+}
+
+static void set_host_vbus(struct exynos_usb_switch *usb_switch, int value)
+{
+ gpio_set_value(usb_switch->gpio_host_vbus, value);
+}
+
+static int exynos_change_usb_mode(struct exynos_usb_switch *usb_switch,
+ enum usb_cable_status mode)
+{
+ struct s3c_udc *udc;
+ struct exynos_ss_udc *ss_udc;
+ unsigned long cur_mode = usb_switch->connect;
+ int ret = 0;
+
+ if (test_bit(USB_DEVICE_ATTACHED, &cur_mode) ||
+ test_bit(USB_HOST_ATTACHED, &cur_mode)) {
+ if (mode == USB_DEVICE_ATTACHED ||
+ mode == USB_HOST_ATTACHED) {
+ printk(KERN_DEBUG "Skip request %d, current %lu\n",
+ mode, cur_mode);
+ return -EPERM;
+ }
+ } else if (test_bit(USB_DRD_DEVICE_ATTACHED, &cur_mode) ||
+ test_bit(USB_DRD_HOST_ATTACHED, &cur_mode)) {
+ if (mode == USB_DRD_DEVICE_ATTACHED ||
+ mode == USB_DRD_HOST_ATTACHED) {
+ printk(KERN_DEBUG "Skip request %d, current %lu\n",
+ mode, cur_mode);
+ return -EPERM;
+ }
+ }
+
+ if (!test_bit(USB_DEVICE_ATTACHED, &cur_mode) &&
+ mode == USB_DEVICE_DETACHED) {
+ printk(KERN_DEBUG "Skip request %d, current %lu\n",
+ mode, cur_mode);
+ return -EPERM;
+ } else if (!test_bit(USB_HOST_ATTACHED, &cur_mode) &&
+ mode == USB_HOST_DETACHED) {
+ printk(KERN_DEBUG "Skip request %d, current %lu\n",
+ mode, cur_mode);
+ return -EPERM;
+ } else if (!test_bit(USB_DRD_DEVICE_ATTACHED, &cur_mode) &&
+ mode == USB_DRD_DEVICE_DETACHED) {
+ printk(KERN_DEBUG "Skip request %d, current %lu\n",
+ mode, cur_mode);
+ return -EPERM;
+ } else if (!test_bit(USB_DRD_HOST_ATTACHED, &cur_mode) &&
+ mode == USB_DRD_HOST_DETACHED) {
+ printk(KERN_DEBUG "Skip request %d, current %lu\n",
+ mode, cur_mode);
+ return -EPERM;
+ }
+
+ switch (mode) {
+ case USB_DEVICE_DETACHED:
+ if (test_bit(USB_HOST_ATTACHED, &cur_mode)) {
+ printk(KERN_ERR "Abnormal request %d, current %lu\n",
+ mode, cur_mode);
+ return -EPERM;
+ }
+ udc = dev_get_drvdata(usb_switch->s3c_udc_dev);
+ if (udc && udc->gadget.ops && udc->gadget.ops->vbus_session)
+ udc->gadget.ops->vbus_session(&udc->gadget, 0);
+ clear_bit(USB_DEVICE_ATTACHED, &usb_switch->connect);
+ break;
+ case USB_DEVICE_ATTACHED:
+ udc = dev_get_drvdata(usb_switch->s3c_udc_dev);
+ if (udc && udc->gadget.ops && udc->gadget.ops->vbus_session)
+ udc->gadget.ops->vbus_session(&udc->gadget, 1);
+ set_bit(USB_DEVICE_ATTACHED, &usb_switch->connect);
+ break;
+ case USB_HOST_DETACHED:
+ if (test_bit(USB_DEVICE_ATTACHED, &cur_mode)) {
+ printk(KERN_ERR "Abnormal request %d, current %lu\n",
+ mode, cur_mode);
+ return -EPERM;
+ }
+ if (usb_switch->ohci_dev)
+ pm_runtime_put(usb_switch->ohci_dev);
+ if (usb_switch->ehci_dev)
+ pm_runtime_put(usb_switch->ehci_dev);
+ if (usb_switch->gpio_host_vbus)
+ set_host_vbus(usb_switch, 0);
+
+#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS)
+ exynos_usb_cable_disconnect();
+#endif
+ clear_bit(USB_HOST_ATTACHED, &usb_switch->connect);
+ break;
+ case USB_HOST_ATTACHED:
+#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS)
+ exynos_usb_cable_connect();
+#endif
+ if (usb_switch->gpio_host_vbus)
+ set_host_vbus(usb_switch, 1);
+
+ if (usb_switch->ehci_dev)
+ pm_runtime_get_sync(usb_switch->ehci_dev);
+ if (usb_switch->ohci_dev)
+ pm_runtime_get_sync(usb_switch->ohci_dev);
+ set_bit(USB_HOST_ATTACHED, &usb_switch->connect);
+ break;
+ case USB_DRD_DEVICE_DETACHED:
+ if (test_bit(USB_DRD_HOST_ATTACHED, &cur_mode)) {
+ printk(KERN_ERR "Abnormal request %d, current %lu\n",
+ mode, cur_mode);
+ return -EPERM;
+ }
+
+ ss_udc = dev_get_drvdata(usb_switch->exynos_udc_dev);
+ if (ss_udc && ss_udc->gadget.ops &&
+ ss_udc->gadget.ops->vbus_session)
+ ss_udc->gadget.ops->vbus_session(&ss_udc->gadget, 0);
+ clear_bit(USB_DRD_DEVICE_ATTACHED, &usb_switch->connect);
+ break;
+ case USB_DRD_DEVICE_ATTACHED:
+ ss_udc = dev_get_drvdata(usb_switch->exynos_udc_dev);
+ if (ss_udc && ss_udc->gadget.ops &&
+ ss_udc->gadget.ops->vbus_session)
+ ss_udc->gadget.ops->vbus_session(&ss_udc->gadget, 1);
+ set_bit(USB_DRD_DEVICE_ATTACHED, &usb_switch->connect);
+ break;
+ case USB_DRD_HOST_DETACHED:
+ if (test_bit(USB_DRD_DEVICE_ATTACHED, &cur_mode)) {
+ printk(KERN_ERR "Abnormal request %d, current %lu\n",
+ mode, cur_mode);
+ return -EPERM;
+ }
+
+ if (usb_switch->xhci_dev)
+ pm_runtime_put_sync(usb_switch->xhci_dev);
+#if defined(CONFIG_BATTERY_SAMSUNG)
+ exynos_usb_cable_disconnect();
+#endif
+ clear_bit(USB_DRD_HOST_ATTACHED, &usb_switch->connect);
+ break;
+ case USB_DRD_HOST_ATTACHED:
+#if defined(CONFIG_BATTERY_SAMSUNG)
+ exynos_usb_cable_connect();
+#endif
+ if (usb_switch->xhci_dev)
+ pm_runtime_get_sync(usb_switch->xhci_dev);
+ set_bit(USB_DRD_HOST_ATTACHED, &usb_switch->connect);
+ break;
+ default:
+ printk(KERN_ERR "Does not changed\n");
+ }
+ printk(KERN_INFO "usb cable = %d\n", mode);
+
+ return ret;
+}
+
+static void exnos_usb_switch_worker(struct work_struct *work)
+{
+ struct exynos_usb_switch *usb_switch =
+ container_of(work, struct exynos_usb_switch, switch_work);
+ int cnt = 0;
+
+ mutex_lock(&usb_switch->mutex);
+ /* If already device detached or host_detected, */
+ if (!is_device_detect(usb_switch) || is_host_detect(usb_switch))
+ goto done;
+ if (!usb_switch->ehci_dev || !usb_switch->ohci_dev)
+ goto detect;
+
+ while (!pm_runtime_suspended(usb_switch->ehci_dev) ||
+ !pm_runtime_suspended(usb_switch->ohci_dev)) {
+
+ mutex_unlock(&usb_switch->mutex);
+ msleep(SWITCH_WAIT_TIME);
+ mutex_lock(&usb_switch->mutex);
+
+ /* If already device detached or host_detected, */
+ if (!is_device_detect(usb_switch) || is_host_detect(usb_switch))
+ goto done;
+
+ if (cnt++ > WAIT_TIMES) {
+ printk(KERN_ERR "%s:device not attached by host\n",
+ __func__);
+ goto done;
+ }
+
+ }
+
+ if (cnt > 1)
+ printk(KERN_INFO "Device wait host power during %d\n", (cnt-1));
+detect:
+ /* Check Device, VBUS PIN high active */
+ exynos_change_usb_mode(usb_switch, USB_DEVICE_ATTACHED);
+done:
+ mutex_unlock(&usb_switch->mutex);
+}
+
+static void exnos_usb_drd_switch_worker(struct work_struct *work)
+{
+ struct exynos_usb_switch *usb_switch =
+ container_of(work, struct exynos_usb_switch, switch_drd_work);
+ int cnt = 0;
+
+ mutex_lock(&usb_switch->mutex);
+ /* If already device detached or host_detected, */
+ if (!is_drd_device_detect(usb_switch) || is_drd_host_detect(usb_switch))
+ goto done;
+ if (!usb_switch->xhci_dev)
+ goto detect;
+
+ while (!pm_runtime_suspended(usb_switch->xhci_dev) ||
+ usb_switch->xhci_dev->power.is_suspended) {
+ mutex_unlock(&usb_switch->mutex);
+ msleep(SWITCH_WAIT_TIME);
+ mutex_lock(&usb_switch->mutex);
+
+ /* If already device detached or host_detected, */
+ if (!is_drd_device_detect(usb_switch) ||
+ is_drd_host_detect(usb_switch))
+ goto done;
+
+ if (cnt++ > WAIT_TIMES) {
+ printk(KERN_ERR "%s:device not attached by host\n",
+ __func__);
+ goto done;
+ }
+
+ }
+
+ if (cnt > 1)
+ printk(KERN_INFO "Device wait host power during %d\n", (cnt-1));
+
+detect:
+ /* Check Device, VBUS PIN high active */
+ exynos_change_usb_mode(usb_switch, USB_DRD_DEVICE_ATTACHED);
+done:
+ mutex_unlock(&usb_switch->mutex);
+}
+
+static irqreturn_t exynos_host_detect_thread(int irq, void *data)
+{
+ struct exynos_usb_switch *usb_switch = data;
+
+ mutex_lock(&usb_switch->mutex);
+
+ if (is_host_detect(usb_switch))
+ exynos_change_usb_mode(usb_switch, USB_HOST_ATTACHED);
+ else
+ exynos_change_usb_mode(usb_switch, USB_HOST_DETACHED);
+
+ mutex_unlock(&usb_switch->mutex);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t exynos_device_detect_thread(int irq, void *data)
+{
+ struct exynos_usb_switch *usb_switch = data;
+
+ mutex_lock(&usb_switch->mutex);
+
+ /* Debounce connect delay */
+ msleep(20);
+
+ if (is_host_detect(usb_switch))
+ printk(KERN_DEBUG "Not expected situation\n");
+ else if (is_device_detect(usb_switch)) {
+ if (usb_switch->gpio_host_vbus)
+ exynos_change_usb_mode(usb_switch, USB_DEVICE_ATTACHED);
+ else
+ queue_work(usb_switch->workqueue, &usb_switch->switch_work);
+ } else {
+ /* VBUS PIN low */
+ exynos_change_usb_mode(usb_switch, USB_DEVICE_DETACHED);
+ }
+
+ mutex_unlock(&usb_switch->mutex);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t exynos_drd_host_detect_thread(int irq, void *data)
+{
+ struct exynos_usb_switch *usb_switch = data;
+
+ mutex_lock(&usb_switch->mutex);
+
+ if (is_drd_host_detect(usb_switch))
+ exynos_change_usb_mode(usb_switch, USB_DRD_HOST_ATTACHED);
+ else
+ exynos_change_usb_mode(usb_switch, USB_DRD_HOST_DETACHED);
+
+ mutex_unlock(&usb_switch->mutex);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t exynos_drd_device_detect_thread(int irq, void *data)
+{
+ struct exynos_usb_switch *usb_switch = data;
+
+ mutex_lock(&usb_switch->mutex);
+
+ /* Debounce connect delay */
+ msleep(20);
+
+ if (is_drd_host_detect(usb_switch))
+ printk(KERN_DEBUG "Not expected situation\n");
+ else if (is_drd_device_detect(usb_switch))
+ queue_work(usb_switch->workqueue, &usb_switch->switch_drd_work);
+ else {
+ /* VBUS PIN low */
+ exynos_change_usb_mode(usb_switch, USB_DRD_DEVICE_DETACHED);
+ }
+
+ mutex_unlock(&usb_switch->mutex);
+
+ return IRQ_HANDLED;
+}
+
+static int exynos_usb_status_init(struct exynos_usb_switch *usb_switch)
+{
+ mutex_lock(&usb_switch->mutex);
+
+ /* 2.0 USB */
+ if (!test_bit(USB_DRD_DEVICE_ATTACHED, &usb_switch->connect) ||
+ !test_bit(USB_DRD_HOST_ATTACHED, &usb_switch->connect)) {
+ if (is_host_detect(usb_switch))
+ exynos_change_usb_mode(usb_switch, USB_HOST_ATTACHED);
+ else if (is_device_detect(usb_switch)) {
+ if (usb_switch->gpio_host_vbus)
+ exynos_change_usb_mode(usb_switch,
+ USB_DEVICE_ATTACHED);
+ else
+ queue_work(usb_switch->workqueue,
+ &usb_switch->switch_work);
+ }
+ }
+
+ /* 3.0 USB */
+ if (!test_bit(USB_DRD_DEVICE_ATTACHED, &usb_switch->connect) ||
+ !test_bit(USB_DRD_HOST_ATTACHED, &usb_switch->connect)) {
+ if (is_drd_host_detect(usb_switch))
+ exynos_change_usb_mode(usb_switch,
+ USB_DRD_HOST_ATTACHED);
+ else if (is_drd_device_detect(usb_switch))
+ queue_work(usb_switch->workqueue,
+ &usb_switch->switch_drd_work);
+ }
+
+ mutex_unlock(&usb_switch->mutex);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int exynos_usbswitch_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos_usb_switch *usb_switch = platform_get_drvdata(pdev);
+
+ dev_dbg(dev, "%s\n", __func__);
+ mutex_lock(&usb_switch->mutex);
+ if (test_bit(USB_DEVICE_ATTACHED, &usb_switch->connect))
+ exynos_change_usb_mode(usb_switch, USB_DEVICE_DETACHED);
+
+ if (test_bit(USB_DRD_DEVICE_ATTACHED, &usb_switch->connect))
+ exynos_change_usb_mode(usb_switch, USB_DRD_DEVICE_DETACHED);
+
+ usb_switch->connect = 0;
+ mutex_unlock(&usb_switch->mutex);
+
+ return 0;
+}
+
+static int exynos_usbswitch_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos_usb_switch *usb_switch = platform_get_drvdata(pdev);
+
+ dev_dbg(dev, "%s\n", __func__);
+ exynos_usb_status_init(usb_switch);
+
+ return 0;
+}
+#else
+#define exynos_usbswitch_suspend NULL
+#define exynos_usbswitch_resume NULL
+#endif
+
+static int __devinit exynos_usbswitch_probe(struct platform_device *pdev)
+{
+ struct s5p_usbswitch_platdata *pdata = dev_get_platdata(&pdev->dev);
+ struct device *dev = &pdev->dev;
+ struct exynos_usb_switch *usb_switch;
+ int irq;
+ int ret = 0;
+
+ usb_switch = kzalloc(sizeof(struct exynos_usb_switch), GFP_KERNEL);
+ if (!usb_switch) {
+ ret = -ENOMEM;
+ return ret;
+ }
+
+ our_switch = usb_switch;
+ mutex_init(&usb_switch->mutex);
+ usb_switch->workqueue = create_singlethread_workqueue("usb_switch");
+ INIT_WORK(&usb_switch->switch_work, exnos_usb_switch_worker);
+ INIT_WORK(&usb_switch->switch_drd_work, exnos_usb_drd_switch_worker);
+
+ usb_switch->gpio_host_detect = pdata->gpio_host_detect;
+ usb_switch->gpio_device_detect = pdata->gpio_device_detect;
+ usb_switch->gpio_host_vbus = pdata->gpio_host_vbus;
+ usb_switch->gpio_drd_host_detect = pdata->gpio_drd_host_detect;
+ usb_switch->gpio_drd_device_detect = pdata->gpio_drd_device_detect;
+
+ usb_switch->ehci_dev = pdata->ehci_dev;
+ usb_switch->ohci_dev = pdata->ohci_dev;
+ usb_switch->xhci_dev = pdata->xhci_dev;
+ usb_switch->s3c_udc_dev = pdata->s3c_udc_dev;
+ usb_switch->exynos_udc_dev = pdata->exynos_udc_dev;
+
+ /* USB Device detect IRQ */
+ irq = platform_get_irq(pdev, 1);
+ if (irq > 0 && usb_switch->s3c_udc_dev) {
+ ret = request_threaded_irq(irq, NULL,
+ exynos_device_detect_thread,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "DEVICE_DETECT", usb_switch);
+ if (ret) {
+ dev_err(dev, "Failed to request device irq %d\n", irq);
+ goto fail;
+ }
+ usb_switch->device_detect_irq = irq;
+ } else if (usb_switch->s3c_udc_dev)
+ exynos_change_usb_mode(usb_switch, USB_DEVICE_ATTACHED);
+ else
+ dev_info(dev, "Disable device detect IRQ\n");
+
+ /* USB Host detect IRQ */
+ irq = platform_get_irq(pdev, 0);
+ if (irq > 0 && (usb_switch->ehci_dev || usb_switch->ohci_dev)) {
+ ret = request_threaded_irq(irq, NULL,
+ exynos_host_detect_thread,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "HOST_DETECT", usb_switch);
+ if (ret) {
+ dev_err(dev, "Failed to request host irq %d\n", irq);
+ goto fail_gpio_device_detect;
+ }
+ usb_switch->host_detect_irq = irq;
+ } else if (usb_switch->ehci_dev || usb_switch->ohci_dev)
+ exynos_change_usb_mode(usb_switch, USB_HOST_ATTACHED);
+ else
+ dev_info(dev, "Disable host detect IRQ\n");
+
+
+ /* USB DRD Device detect IRQ */
+ irq = platform_get_irq(pdev, 3);
+ if (irq > 0 && usb_switch->exynos_udc_dev) {
+ ret = request_threaded_irq(irq, NULL,
+ exynos_drd_device_detect_thread,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "DRD_DEVICE_DETECT", usb_switch);
+ if (ret) {
+ dev_err(dev, "Failed to request drd device irq %d\n",
+ irq);
+ goto fail_gpio_host_detect;
+ }
+ usb_switch->device_drd_detect_irq = irq;
+ } else if (usb_switch->exynos_udc_dev)
+ exynos_change_usb_mode(usb_switch, USB_DRD_DEVICE_ATTACHED);
+ else
+ dev_info(dev, "Disable drd device detect IRQ\n");
+
+ /* USB DRD Host detect IRQ */
+ irq = platform_get_irq(pdev, 2);
+ if (irq > 0 && usb_switch->xhci_dev) {
+ ret = request_threaded_irq(irq, NULL,
+ exynos_drd_host_detect_thread,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "DRD_HOST_DETECT", usb_switch);
+ if (ret) {
+ dev_err(dev, "Failed to request drd host irq %d\n",
+ irq);
+ goto fail_gpio_drd_device_detect;
+ }
+ usb_switch->host_drd_detect_irq = irq;
+ } else if (usb_switch->xhci_dev)
+ exynos_change_usb_mode(usb_switch, USB_DRD_HOST_ATTACHED);
+ else
+ dev_info(dev, "Disable drd host detect IRQ\n");
+
+ exynos_usb_status_init(usb_switch);
+
+ platform_set_drvdata(pdev, usb_switch);
+
+ return ret;
+fail_gpio_drd_device_detect:
+ free_irq(usb_switch->device_drd_detect_irq, usb_switch);
+fail_gpio_host_detect:
+ free_irq(usb_switch->host_detect_irq, usb_switch);
+fail_gpio_device_detect:
+ free_irq(usb_switch->device_detect_irq, usb_switch);
+fail:
+ cancel_work_sync(&usb_switch->switch_work);
+ destroy_workqueue(usb_switch->workqueue);
+ mutex_destroy(&usb_switch->mutex);
+ kfree(usb_switch);
+ return ret;
+}
+
+static int __devexit exynos_usbswitch_remove(struct platform_device *pdev)
+{
+ struct exynos_usb_switch *usb_switch = platform_get_drvdata(pdev);
+
+ free_irq(usb_switch->host_drd_detect_irq, usb_switch);
+ free_irq(usb_switch->device_drd_detect_irq, usb_switch);
+ free_irq(usb_switch->host_detect_irq, usb_switch);
+ free_irq(usb_switch->device_detect_irq, usb_switch);
+ platform_set_drvdata(pdev, 0);
+
+ cancel_work_sync(&usb_switch->switch_work);
+ destroy_workqueue(usb_switch->workqueue);
+ mutex_destroy(&usb_switch->mutex);
+ kfree(usb_switch);
+
+ return 0;
+}
+
+static const struct dev_pm_ops exynos_usbswitch_pm_ops = {
+ .suspend = exynos_usbswitch_suspend,
+ .resume = exynos_usbswitch_resume,
+};
+
+static struct platform_driver exynos_usbswitch_driver = {
+ .probe = exynos_usbswitch_probe,
+ .remove = __devexit_p(exynos_usbswitch_remove),
+ .driver = {
+ .name = "exynos-usb-switch",
+ .owner = THIS_MODULE,
+ .pm = &exynos_usbswitch_pm_ops,
+ },
+};
+
+static int __init exynos_usbswitch_init(void)
+{
+ int ret;
+
+ ret = platform_device_register(&s5p_device_usbswitch);
+ if (ret < 0)
+ return ret;
+
+ ret = platform_driver_register(&exynos_usbswitch_driver);
+ if (!ret)
+ printk(KERN_INFO "%s: " DRIVER_DESC "\n", switch_name);
+
+ return ret;
+}
+late_initcall(exynos_usbswitch_init);
+
+static void __exit exynos_usbswitch_exit(void)
+{
+ platform_driver_unregister(&exynos_usbswitch_driver);
+}
+module_exit(exynos_usbswitch_exit);
+
+MODULE_DESCRIPTION("Exynos USB switch driver");
+MODULE_AUTHOR("<yulgon.kim@samsung.com>");
+MODULE_LICENSE("GPL");