aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/misc/modem_if_na/modem_link_pm_usb.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/misc/modem_if_na/modem_link_pm_usb.c')
-rw-r--r--drivers/misc/modem_if_na/modem_link_pm_usb.c370
1 files changed, 370 insertions, 0 deletions
diff --git a/drivers/misc/modem_if_na/modem_link_pm_usb.c b/drivers/misc/modem_if_na/modem_link_pm_usb.c
new file mode 100644
index 0000000..4907de7
--- /dev/null
+++ b/drivers/misc/modem_if_na/modem_link_pm_usb.c
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2012 Samsung Electronics.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+/*
+ * TODO (last updated Apr 13, 2012):
+ * - move usb hub feature to board file
+ * currently, some code was commented out by CONFIG_USBHUB_USB3503
+ * - Both usb_ld and link_pm_data have same gpio member.
+ * we have to remove it from one of them and
+ * make helper function for gpio handling.
+ */
+
+#define DEBUG
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/sched.h>
+#include <linux/irq.h>
+#include <linux/poll.h>
+#include <linux/gpio.h>
+#include <linux/if_arp.h>
+#include <linux/platform_device.h>
+#include <linux/suspend.h>
+
+#include "modem_link_pm_usb.h"
+
+bool link_pm_is_connected(struct usb_link_device *usb_ld)
+{
+#ifdef CONFIG_USBHUB_USB3503
+ if (has_hub(usb_ld)) {
+ if (usb_ld->link_pm_data->hub_status == HUB_STATE_RESUMMING
+ || usb_ld->link_pm_data->hub_init_lock)
+ return false;
+
+ if (usb_ld->link_pm_data->hub_status == HUB_STATE_OFF) {
+ schedule_delayed_work(
+ &usb_ld->link_pm_data->link_pm_hub, 0);
+ return false;
+ }
+ }
+#endif
+ if (!usb_ld->if_usb_connected) {
+ mif_err("if not connected\n");
+ return false;
+ }
+
+ return true;
+}
+
+#ifdef CONFIG_USBHUB_USB3503
+static void link_pm_hub_work(struct work_struct *work)
+{
+ int err;
+ struct link_pm_data *pm_data =
+ container_of(work, struct link_pm_data, link_pm_hub.work);
+
+ if (pm_data->hub_status == HUB_STATE_ACTIVE)
+ return;
+
+ if (!pm_data->port_enable) {
+ mif_err("mif: hub power func not assinged\n");
+ return;
+ }
+ wake_lock(&pm_data->hub_lock);
+
+ /* If kernel if suspend, wait the ehci resume */
+ if (pm_data->dpm_suspending) {
+ mif_info("dpm_suspending\n");
+ schedule_delayed_work(&pm_data->link_pm_hub,
+ msecs_to_jiffies(500));
+ goto exit;
+ }
+
+ switch (pm_data->hub_status) {
+ case HUB_STATE_OFF:
+ pm_data->hub_status = HUB_STATE_RESUMMING;
+ mif_trace("hub off->on\n");
+
+ /* skip 1st time before first probe */
+ if (pm_data->root_hub)
+ pm_runtime_get_sync(pm_data->root_hub);
+ err = pm_data->port_enable(2, 1);
+ if (err < 0) {
+ mif_err("hub on fail err=%d\n", err);
+ err = pm_data->port_enable(2, 0);
+ if (err < 0)
+ mif_err("hub off fail err=%d\n", err);
+ pm_data->hub_status = HUB_STATE_OFF;
+ if (pm_data->root_hub)
+ pm_runtime_put_sync(pm_data->root_hub);
+ goto exit;
+ }
+ /* resume root hub */
+ schedule_delayed_work(&pm_data->link_pm_hub,
+ msecs_to_jiffies(100));
+ break;
+ case HUB_STATE_RESUMMING:
+ if (pm_data->hub_on_retry_cnt++ > 50) {
+ pm_data->hub_on_retry_cnt = 0;
+ pm_data->hub_status = HUB_STATE_OFF;
+ if (pm_data->root_hub)
+ pm_runtime_put_sync(pm_data->root_hub);
+ }
+ mif_trace("hub resumming\n");
+ schedule_delayed_work(&pm_data->link_pm_hub,
+ msecs_to_jiffies(200));
+ break;
+ case HUB_STATE_PREACTIVE:
+ mif_trace("hub active\n");
+ pm_data->hub_on_retry_cnt = 0;
+ wake_unlock(&pm_data->hub_lock);
+ pm_data->hub_status = HUB_STATE_ACTIVE;
+ complete(&pm_data->hub_active);
+ if (pm_data->root_hub)
+ pm_runtime_put_sync(pm_data->root_hub);
+ break;
+ }
+exit:
+ return;
+}
+#endif
+
+static int link_pm_hub_standby(struct link_pm_data *pm_data)
+{
+ int err = 0;
+
+#ifdef CONFIG_USBHUB_USB3503
+ mif_info("wait hub standby\n");
+
+ if (!pm_data->port_enable) {
+ mif_err("hub power func not assinged\n");
+ return -ENODEV;
+ }
+
+ err = pm_data->port_enable(2, 0);
+ if (err < 0)
+ mif_err("hub off fail err=%d\n", err);
+
+ pm_data->hub_status = HUB_STATE_OFF;
+#endif
+ return err;
+}
+
+bool link_pm_set_active(struct usb_link_device *usb_ld)
+{
+ int ret;
+ struct link_pm_data *pm_data = usb_ld->link_pm_data;
+
+ if (has_hub(usb_ld)) {
+ if (pm_data->hub_status != HUB_STATE_ACTIVE) {
+ INIT_COMPLETION(pm_data->hub_active);
+ SET_SLAVE_WAKEUP(usb_ld->pdata, 1);
+ ret = wait_for_completion_timeout(&pm_data->hub_active,
+ msecs_to_jiffies(2000));
+ if (!ret) { /*timeout*/
+ mif_err("hub on timeout - retry\n");
+ SET_SLAVE_WAKEUP(usb_ld->pdata, 0);
+ queue_delayed_work(usb_ld->ld.tx_wq,
+ &usb_ld->ld.tx_delayed_work, 0);
+ return false;
+ }
+ }
+ } else {
+ /* TODO do something */
+ }
+ return true;
+}
+
+static long link_pm_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int value, err = 0;
+ struct task_struct *task = get_current();
+ struct link_pm_data *pm_data = file->private_data;
+ struct usb_link_device *usb_ld = pm_data->usb_ld;
+ char taskname[TASK_COMM_LEN];
+
+ mif_info("cmd: 0x%08x\n", cmd);
+
+ switch (cmd) {
+ case IOCTL_LINK_CONTROL_ACTIVE:
+ if (copy_from_user(&value, (const void __user *)arg,
+ sizeof(int)))
+ return -EFAULT;
+ gpio_set_value(pm_data->gpio_link_active, value);
+ break;
+ case IOCTL_LINK_GET_HOSTWAKE:
+ return !gpio_get_value(pm_data->gpio_link_hostwake);
+ case IOCTL_LINK_CONNECTED:
+ return pm_data->usb_ld->if_usb_connected;
+ case IOCTL_LINK_PORT_ON: /* hub only */
+ /* ignore cp host wakeup irq, set the hub_init_lock when AP try
+ CP off and release hub_init_lock when CP boot done */
+ pm_data->hub_init_lock = 0;
+ if (pm_data->root_hub) {
+ pm_runtime_resume(pm_data->root_hub);
+ pm_runtime_forbid(pm_data->root_hub->parent);
+ }
+ if (pm_data->port_enable) {
+ err = pm_data->port_enable(2, 1);
+ if (err < 0) {
+ mif_err("hub on fail err=%d\n", err);
+ goto exit;
+ }
+ pm_data->hub_status = HUB_STATE_RESUMMING;
+ }
+ break;
+ case IOCTL_LINK_PORT_OFF: /* hub only */
+ err = link_pm_hub_standby(pm_data);
+ if (err < 0) {
+ mif_err("usb3503 active fail\n");
+ goto exit;
+ }
+ pm_data->hub_init_lock = 1;
+ pm_data->hub_handshake_done = 0;
+
+ break;
+ case IOCTL_LINK_BLOCK_AUTOSUSPEND: /* block autosuspend forever */
+ mif_info("blocked autosuspend by `%s(%d)'\n",
+ get_task_comm(taskname, task), task->pid);
+ pm_data->block_autosuspend = true;
+ if (usb_ld->usbdev)
+ pm_runtime_forbid(&usb_ld->usbdev->dev);
+ break;
+ case IOCTL_LINK_ENABLE_AUTOSUSPEND: /* Enable autosuspend */
+ mif_info("autosuspend enabled by `%s(%d)'\n",
+ get_task_comm(taskname, task), task->pid);
+ pm_data->block_autosuspend = false;
+ if (usb_ld->usbdev)
+ pm_runtime_allow(&usb_ld->usbdev->dev);
+ else {
+ mif_err("Enable autosuspend failed\n");
+ err = -ENODEV;
+ }
+ break;
+ default:
+ break;
+ }
+exit:
+ return err;
+}
+
+static int link_pm_open(struct inode *inode, struct file *file)
+{
+ struct link_pm_data *pm_data =
+ (struct link_pm_data *)file->private_data;
+ file->private_data = (void *)pm_data;
+ return 0;
+}
+
+static int link_pm_release(struct inode *inode, struct file *file)
+{
+ file->private_data = NULL;
+ return 0;
+}
+
+static const struct file_operations link_pm_fops = {
+ .owner = THIS_MODULE,
+ .open = link_pm_open,
+ .release = link_pm_release,
+ .unlocked_ioctl = link_pm_ioctl,
+};
+
+static int link_pm_notifier_event(struct notifier_block *this,
+ unsigned long event, void *ptr)
+{
+ struct link_pm_data *pm_data =
+ container_of(this, struct link_pm_data, pm_notifier);
+
+ switch (event) {
+ case PM_SUSPEND_PREPARE:
+ pm_data->dpm_suspending = true;
+ link_pm_hub_standby(pm_data);
+ return NOTIFY_OK;
+ case PM_POST_SUSPEND:
+ pm_data->dpm_suspending = false;
+ return NOTIFY_OK;
+ }
+ return NOTIFY_DONE;
+}
+
+int link_pm_init(struct usb_link_device *usb_ld, void *data)
+{
+ int err;
+ int irq;
+ struct platform_device *pdev = (struct platform_device *)data;
+ struct modem_data *pdata =
+ (struct modem_data *)pdev->dev.platform_data;
+ struct modemlink_pm_data *pm_pdata = pdata->link_pm_data;
+ struct link_pm_data *pm_data =
+ kzalloc(sizeof(struct link_pm_data), GFP_KERNEL);
+ if (!pm_data) {
+ mif_err("link_pm_data is NULL\n");
+ return -ENOMEM;
+ }
+ /* get link pm data from modemcontrol's platform data */
+ pm_data->gpio_link_active = pm_pdata->gpio_link_active;
+ pm_data->gpio_link_hostwake = pm_pdata->gpio_link_hostwake;
+ pm_data->gpio_link_slavewake = pm_pdata->gpio_link_slavewake;
+ pm_data->link_reconnect = pm_pdata->link_reconnect;
+ pm_data->port_enable = pm_pdata->port_enable;
+ pm_data->cpufreq_lock = pm_pdata->cpufreq_lock;
+ pm_data->cpufreq_unlock = pm_pdata->cpufreq_unlock;
+ pm_data->autosuspend_delay_ms = pm_pdata->autosuspend_delay_ms;
+ pm_data->block_autosuspend = false;
+
+ pm_data->usb_ld = usb_ld;
+ pm_data->link_pm_active = false;
+ usb_ld->link_pm_data = pm_data;
+
+ pm_data->miscdev.minor = MISC_DYNAMIC_MINOR;
+ pm_data->miscdev.name = "link_pm";
+ pm_data->miscdev.fops = &link_pm_fops;
+
+ err = misc_register(&pm_data->miscdev);
+ if (err < 0) {
+ mif_err("fail to register pm device(%d)\n", err);
+ goto err_misc_register;
+ }
+
+ pm_data->hub_init_lock = 1;
+ irq = gpio_to_irq(usb_ld->pdata->gpio_host_wakeup);
+ err = request_threaded_irq(irq, NULL, usb_resume_irq,
+ IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "modem_usb_wake", usb_ld);
+ if (err) {
+ mif_err("Failed to allocate an interrupt(%d)\n", irq);
+ goto err_request_irq;
+ }
+ enable_irq_wake(irq);
+
+ pm_data->has_usbhub = pm_pdata->has_usbhub;
+
+#ifdef CONFIG_USBHUB_USB3503
+ if (has_hub(usb_ld)) {
+ init_completion(&pm_data->hub_active);
+ pm_data->hub_status = HUB_STATE_OFF;
+ pm_pdata->p_hub_status = &pm_data->hub_status;
+ pm_data->hub_handshake_done = 0;
+ pm_data->root_hub = NULL;
+ wake_lock_init(&pm_data->hub_lock, WAKE_LOCK_SUSPEND,
+ "modem_hub_enum_lock");
+ INIT_DELAYED_WORK(&pm_data->link_pm_hub, link_pm_hub_work);
+ }
+#endif
+
+ wake_lock_init(&pm_data->boot_wake, WAKE_LOCK_SUSPEND, "boot_usb");
+
+ pm_data->pm_notifier.notifier_call = link_pm_notifier_event;
+ register_pm_notifier(&pm_data->pm_notifier);
+
+ return 0;
+
+err_request_irq:
+ misc_deregister(&pm_data->miscdev);
+err_misc_register:
+ kfree(pm_data);
+ return err;
+}