aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-exynos/mdm_hsic_pm.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-exynos/mdm_hsic_pm.c')
-rw-r--r--arch/arm/mach-exynos/mdm_hsic_pm.c1048
1 files changed, 1048 insertions, 0 deletions
diff --git a/arch/arm/mach-exynos/mdm_hsic_pm.c b/arch/arm/mach-exynos/mdm_hsic_pm.c
new file mode 100644
index 0000000..7d8ee80
--- /dev/null
+++ b/arch/arm/mach-exynos/mdm_hsic_pm.c
@@ -0,0 +1,1048 @@
+/* linux/arch/arm/mach-xxxx/mdm_hsic_pm.c
+ * Copyright (C) 2010 Samsung Electronics. All rights reserved.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/kernel.h>
+#include <linux/irq.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/usb.h>
+#include <linux/pm_runtime.h>
+#include <plat/gpio-cfg.h>
+#include <linux/mdm_hsic_pm.h>
+#include <linux/suspend.h>
+#include <linux/wakelock.h>
+#include <mach/subsystem_restart.h>
+#include <mach/sec_modem.h>
+#include <linux/msm_charm.h>
+#include "mdm_private.h"
+#include <linux/wakelock.h>
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+#include <linux/usb/ehci_def.h>
+
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <mach/mdm2.h>
+#include <linux/usb/android_composite.h>
+
+#define EXTERNAL_MODEM "external_modem"
+#define EHCI_REG_DUMP
+#define DEFAULT_RAW_WAKE_TIME (6*HZ)
+
+BLOCKING_NOTIFIER_HEAD(mdm_reset_notifier_list);
+
+/**
+ * TODO:
+ * pm notifier register
+ *
+ * think the way to use notifier to register or unregister device
+ *
+ * disconnect also can be notified
+ *
+ * block request under kernel power off seq.
+ *
+ * in suspend function if busy has set, return
+ *
+ */
+
+/**
+ * struct mdm_hsic_pm_data - hsic pm platform driver private data
+ * provide data and information for pm state transition
+ *
+ * @name: name of this pm driver
+ * @udev: usb driver for resuming device from device request
+ * @intf_cnt: count of registered interface driver
+ * @block_request: block and ignore requested resume interrupt
+ * @state_busy: it is not determined to use, it can be replaced to
+ * rpm status check
+ * @pm_notifier: notifier block to control block_request variable
+ * block_reqeust set to true at PM_SUSPEND_PREPARE and
+ * release at PM_POST_RESUME
+ *
+ */
+struct mdm_hsic_pm_data {
+ struct list_head list;
+ char name[32];
+
+ struct usb_device *udev;
+ int intf_cnt;
+
+ /* control variables */
+ struct notifier_block pm_notifier;
+ struct notifier_block netdev_notifier;
+ struct notifier_block usb_composite_notifier;
+
+ bool block_request;
+ bool state_busy;
+ atomic_t pmlock_cnt;
+ bool shutdown;
+
+ /* gpio-s and irq */
+ int gpio_host_ready;
+ int gpio_device_ready;
+ int gpio_host_wake;
+ int irq;
+
+ /* wakelock for L0 - L2 */
+ struct wake_lock l2_wake;
+
+ /* wakelock for boot */
+ struct wake_lock boot_wake;
+
+ /* wakelock for fast dormancy */
+ struct wake_lock fd_wake;
+ long fd_wake_time; /* wake time for raw packet in jiffies */
+
+ /* workqueue, work for delayed autosuspend */
+ struct workqueue_struct *wq;
+ struct delayed_work auto_rpm_start_work;
+ struct delayed_work auto_rpm_restart_work;
+ struct delayed_work request_resume_work;
+ struct delayed_work fast_dormancy_work;
+
+ struct mdm_hsic_pm_platform_data *mdm_pdata;
+};
+
+/* indicate wakeup from lpa state */
+bool lpa_handling;
+
+#ifdef EHCI_REG_DUMP
+struct dump_ehci_regs {
+ unsigned caps_hc_capbase;
+ unsigned caps_hcs_params;
+ unsigned caps_hcc_params;
+ unsigned reserved0;
+ struct ehci_regs regs;
+ unsigned port_usb; /*0x54*/
+ unsigned port_hsic0;
+ unsigned port_hsic1;
+ unsigned reserved[12];
+ unsigned insnreg00; /*0x90*/
+ unsigned insnreg01;
+ unsigned insnreg02;
+ unsigned insnreg03;
+ unsigned insnreg04;
+ unsigned insnreg05;
+ unsigned insnreg06;
+ unsigned insnreg07;
+};
+
+struct s5p_ehci_hcd_stub {
+ struct device *dev;
+ struct usb_hcd *hcd;
+ struct clk *clk;
+ int power_on;
+};
+/* for EHCI register dump */
+struct dump_ehci_regs sec_debug_ehci_regs;
+
+#define pr_hcd(s, r) printk(KERN_DEBUG "hcd reg(%s):\t 0x%08x\n", s, r)
+static void print_ehci_regs(struct dump_ehci_regs *base)
+{
+ pr_hcd("HCCPBASE", base->caps_hc_capbase);
+ pr_hcd("HCSPARAMS", base->caps_hcs_params);
+ pr_hcd("HCCPARAMS", base->caps_hcc_params);
+ pr_hcd("USBCMD", base->regs.command);
+ pr_hcd("USBSTS", base->regs.status);
+ pr_hcd("USBINTR", base->regs.intr_enable);
+ pr_hcd("FRINDEX", base->regs.frame_index);
+ pr_hcd("CTRLDSSEGMENT", base->regs.segment);
+ pr_hcd("PERIODICLISTBASE", base->regs.frame_list);
+ pr_hcd("ASYNCLISTADDR", base->regs.async_next);
+ pr_hcd("CONFIGFLAG", base->regs.configured_flag);
+ pr_hcd("PORT0 Status/Control", base->port_usb);
+ pr_hcd("PORT1 Status/Control", base->port_hsic0);
+ pr_hcd("PORT2 Status/Control", base->port_hsic1);
+ pr_hcd("INSNREG00", base->insnreg00);
+ pr_hcd("INSNREG01", base->insnreg01);
+ pr_hcd("INSNREG02", base->insnreg02);
+ pr_hcd("INSNREG03", base->insnreg03);
+ pr_hcd("INSNREG04", base->insnreg04);
+ pr_hcd("INSNREG05", base->insnreg05);
+ pr_hcd("INSNREG06", base->insnreg06);
+ pr_hcd("INSNREG07", base->insnreg07);
+}
+
+void debug_ehci_reg_dump(struct device *hdev)
+{
+ struct s5p_ehci_hcd_stub *s5p_ehci = dev_get_drvdata(hdev);
+ struct usb_hcd *hcd = s5p_ehci->hcd;
+ char *buf = (char *)&sec_debug_ehci_regs;
+ pr_info("%s\n", __func__);
+ pr_info("EHCI %s, %s\n", dev_driver_string(hdev), dev_name(hdev));
+
+ print_ehci_regs(hcd->regs);
+
+ memcpy(buf, hcd->regs, 0xB);
+ memcpy(buf + 0x10, hcd->regs + 0x10, 0x1F);
+ memcpy(buf + 0x50, hcd->regs + 0x50, 0xF);
+ memcpy(buf + 0x90, hcd->regs + 0x90, 0x1F);
+}
+#else
+#define debug_ehci_reg_dump (NULL)
+#endif
+
+/**
+ * hsic pm device list for multiple modem support
+ */
+static LIST_HEAD(hsic_pm_dev_list);
+
+static void print_pm_dev_info(struct mdm_hsic_pm_data *pm_data)
+{
+ pr_info("pm device\n\tname = %s\n"
+ "\tudev = 0x%p\n"
+ "\tintf_cnt = %d\n"
+ "\tblock_request = %s\n",
+ pm_data->name,
+ pm_data->udev,
+ pm_data->intf_cnt,
+ pm_data->block_request ? "true" : "false");
+}
+
+static struct mdm_hsic_pm_data *get_pm_data_by_dev_name(const char *name)
+{
+ struct mdm_hsic_pm_data *pm_data;
+
+ if (list_empty(&hsic_pm_dev_list)) {
+ pr_err("%s:there's no dev on pm dev list\n", __func__);
+ return NULL;
+ };
+
+ /* get device from list */
+ list_for_each_entry(pm_data, &hsic_pm_dev_list, list) {
+ if (!strncmp(pm_data->name, name, strlen(name)))
+ return pm_data;
+ }
+
+ return NULL;
+}
+
+void notify_modem_fatal(void)
+{
+ struct mdm_hsic_pm_data *pm_data =
+ get_pm_data_by_dev_name("mdm_hsic_pm0");
+
+ pr_info("%s or shutdown\n", __func__);
+
+ if (!pm_data || !pm_data->intf_cnt || !pm_data->udev)
+ return;
+
+ pm_data->shutdown = true;
+
+ /* crash from sleep, ehci is in waking up, so do not control ehci */
+ if (!pm_data->block_request) {
+ struct device *dev, *hdev;
+ hdev = pm_data->udev->bus->root_hub->dev.parent;
+ dev = &pm_data->udev->dev;
+
+ pm_runtime_get_noresume(dev);
+ pm_runtime_dont_use_autosuspend(dev);
+ wake_up_all(&dev->power.wait_queue);
+ pm_runtime_resume(dev);
+ pm_runtime_get_noresume(dev);
+
+ blocking_notifier_call_chain(&mdm_reset_notifier_list, 0, 0);
+ }
+}
+
+void request_autopm_lock(int status)
+{
+ struct mdm_hsic_pm_data *pm_data =
+ get_pm_data_by_dev_name("mdm_hsic_pm0");
+
+ if (!pm_data || !pm_data->udev)
+ return;
+
+ pr_debug("%s: set runtime pm lock : %d\n", __func__, status);
+
+ if (status) {
+ if (!atomic_read(&pm_data->pmlock_cnt)) {
+ atomic_inc(&pm_data->pmlock_cnt);
+ pr_info("get lock\n");
+ pm_runtime_get(&pm_data->udev->dev);
+ pm_runtime_forbid(&pm_data->udev->dev);
+ } else
+ atomic_inc(&pm_data->pmlock_cnt);
+ } else {
+ if (!atomic_read(&pm_data->pmlock_cnt))
+ pr_info("unbalanced release\n");
+ else if (atomic_dec_and_test(&pm_data->pmlock_cnt)) {
+ pr_info("release lock\n");
+ pm_runtime_allow(&pm_data->udev->dev);
+ pm_runtime_put(&pm_data->udev->dev);
+ }
+ }
+}
+
+void request_active_lock_set(const char *name)
+{
+ struct mdm_hsic_pm_data *pm_data = get_pm_data_by_dev_name(name);
+ pr_info("%s\n", __func__);
+ if (pm_data)
+ wake_lock(&pm_data->l2_wake);
+}
+
+void request_active_lock_release(const char *name)
+{
+ struct mdm_hsic_pm_data *pm_data = get_pm_data_by_dev_name(name);
+ pr_info("%s\n", __func__);
+ if (pm_data)
+ wake_unlock(&pm_data->l2_wake);
+}
+
+void request_boot_lock_set(const char *name)
+{
+ struct mdm_hsic_pm_data *pm_data = get_pm_data_by_dev_name(name);
+ pr_info("%s\n", __func__);
+ if (pm_data)
+ wake_lock(&pm_data->boot_wake);
+}
+
+void request_boot_lock_release(const char *name)
+{
+ struct mdm_hsic_pm_data *pm_data = get_pm_data_by_dev_name(name);
+ pr_info("%s\n", __func__);
+ if (pm_data)
+ wake_unlock(&pm_data->boot_wake);
+}
+
+bool check_request_blocked(const char *name)
+{
+ struct mdm_hsic_pm_data *pm_data = get_pm_data_by_dev_name(name);
+ if (!pm_data)
+ return false;
+
+ return pm_data->block_request;
+}
+
+void set_host_stat(const char *name, enum pwr_stat status)
+{
+ /* find pm device from list by name */
+ struct mdm_hsic_pm_data *pm_data = get_pm_data_by_dev_name(name);
+
+ if (!pm_data) {
+ pr_err("%s:no pm device(%s) exist\n", __func__, name);
+ return;
+ }
+
+ if (pm_data->gpio_host_ready) {
+ pr_info("dev rdy val = %d\n",
+ gpio_get_value(pm_data->gpio_device_ready));
+ pr_info("%s:set host port power status to [%d]\n",
+ __func__, status);
+
+ /*10ms delay location moved*/
+ if(status == POWER_OFF)
+ mdelay(10);
+
+ gpio_set_value(pm_data->gpio_host_ready, status);
+ }
+}
+
+#define DEV_POWER_WAIT_SPIN 10
+#define DEV_POWER_WAIT_MS 10
+int wait_dev_pwr_stat(const char *name, enum pwr_stat status)
+{
+ int spin;
+ /* find pm device from list by name */
+ struct mdm_hsic_pm_data *pm_data = get_pm_data_by_dev_name(name);
+
+ if (!pm_data) {
+ pr_err("%s:no pm device(%s) exist\n", __func__, name);
+ return -ENODEV;
+ }
+
+ pr_info("%s:[%s]...\n", __func__, status ? "PWR ON" : "PWR OFF");
+
+ if (pm_data->gpio_device_ready) {
+ for (spin = 0; spin < DEV_POWER_WAIT_SPIN ; spin++) {
+ if (gpio_get_value(pm_data->gpio_device_ready) ==
+ status)
+ break;
+ else
+ mdelay(DEV_POWER_WAIT_MS);
+ }
+ }
+
+ if (gpio_get_value(pm_data->gpio_device_ready) == status)
+ pr_info(" done\n");
+ else
+ subsystem_restart(EXTERNAL_MODEM);
+ return 0;
+}
+
+/**
+ * check suspended state for L3 drive
+ * if not, L3 blocked and stay at L2 / L0 state
+ */
+int check_udev_suspend_allowed(const char *name)
+{
+ struct mdm_hsic_pm_data *pm_data = get_pm_data_by_dev_name(name);
+ struct device *dev;
+
+ if (!pm_data) {
+ pr_err("%s:no pm device(%s) exist\n", __func__, name);
+ return -ENODEV;
+ }
+ if (!pm_data->intf_cnt || !pm_data->udev)
+ return -ENODEV;
+
+ dev = &pm_data->udev->dev;
+
+ pr_info("%s:state_busy = %d, suspended = %d(rpmstat = %d:dpth:%d),"
+ " suspended_child = %d\n", __func__, pm_data->state_busy,
+ pm_runtime_suspended(dev), dev->power.runtime_status,
+ dev->power.disable_depth, pm_children_suspended(dev));
+
+ if (pm_data->state_busy)
+ return -EBUSY;
+
+ return pm_runtime_suspended(dev) && pm_children_suspended(dev);
+}
+
+int set_hsic_lpa_states(int states)
+{
+ /* if modem need to check survive, get status in variable */
+ int val = 1;
+
+ /* set state for LPA enter */
+ if (val) {
+ switch (states) {
+ case STATE_HSIC_LPA_ENTER:
+ /*
+ * need get some delay for MDM9x15 suspend
+ * if L3 drive goes out to modem in suspending
+ * modem goes to unstable PM state. now 10 ms is enough
+ */
+ /*10ms delay location moved*/
+ //mdelay(10);
+ set_host_stat("mdm_hsic_pm0", POWER_OFF);
+ wait_dev_pwr_stat("mdm_hsic_pm0", POWER_OFF);
+ pr_info("set hsic lpa enter\n");
+ break;
+ case STATE_HSIC_LPA_WAKE:
+ /* host control is done by ehci runtime resume code */
+ #if 0
+ set_host_stat("mdm_hsic_pm0", POWER_ON);
+ wait_dev_pwr_stat("mdm_hsic_pm0", POWER_ON);
+ #endif
+ lpa_handling = true;
+ pr_info("%s: set lpa handling to true\n", __func__);
+ request_active_lock_set("mdm_hsic_pm0");
+ pr_info("set hsic lpa wake\n");
+ break;
+ case STATE_HSIC_LPA_PHY_INIT:
+ pr_info("set hsic lpa phy init\n");
+ break;
+ case STATE_HSIC_LPA_CHECK:
+ if (lpcharge)
+ return 0;
+ else
+ if (!get_pm_data_by_dev_name("mdm_hsic_pm0"))
+ return 1;
+ else
+ return 0;
+ default:
+ pr_info("unknown lpa state\n");
+ break;
+ }
+ }
+ return 0;
+}
+
+#define PM_START_DELAY_MS 3000
+int register_udev_to_pm_dev(const char *name, struct usb_device *udev)
+{
+ /* find pm device from list by name */
+ struct mdm_hsic_pm_data *pm_data = get_pm_data_by_dev_name(name);
+
+ if (!pm_data) {
+ pr_err("%s:no pm device(%s) exist for udev(0x%p)\n",
+ __func__, name, udev);
+ return -ENODEV;
+ }
+
+ print_pm_dev_info(pm_data);
+
+ if (!pm_data->intf_cnt) {
+ pr_info("%s: registering new udev(0x%p) to %s\n", __func__,
+ udev, pm_data->name);
+ pm_data->udev = udev;
+ atomic_set(&pm_data->pmlock_cnt, 0);
+ usb_disable_autosuspend(udev);
+ } else if (pm_data->udev && pm_data->udev != udev) {
+ pr_err("%s:udev mismatching: pm_data->udev(0x%p), udev(0x%p)\n",
+ __func__, pm_data->udev, udev);
+ return -EINVAL;
+ }
+
+ pm_data->intf_cnt++;
+ pr_info("%s:udev(0x%p) successfully registerd to %s, intf count = %d\n",
+ __func__, udev, pm_data->name, pm_data->intf_cnt);
+
+ queue_delayed_work(pm_data->wq, &pm_data->auto_rpm_start_work,
+ msecs_to_jiffies(PM_START_DELAY_MS));
+ return 0;
+}
+
+/* force fatal for debug when HSIC disconnect */
+extern void mdm_force_fatal(void);
+
+void unregister_udev_from_pm_dev(const char *name, struct usb_device *udev)
+{
+ /* find pm device from list by name */
+ struct mdm_hsic_pm_data *pm_data = get_pm_data_by_dev_name(name);
+ struct device *hdev;
+
+ pr_info("%s\n", __func__);
+
+ if (!pm_data) {
+ pr_err("%s:no pm device(%s) exist for udev(0x%p)\n",
+ __func__, name, udev);
+ return;
+ }
+
+ if (!pm_data->shutdown) {
+ hdev = udev->bus->root_hub->dev.parent;
+ pm_runtime_forbid(hdev); /*ehci*/
+ debug_ehci_reg_dump(hdev);
+ }
+
+ if (pm_data->udev && pm_data->udev != udev) {
+ pr_err("%s:udev mismatching: pm_data->udev(0x%p), udev(0x%p)\n",
+ __func__, pm_data->udev, udev);
+ return;
+ }
+
+ pm_data->intf_cnt--;
+ pr_info("%s:udev(0x%p) unregistered from %s, intf count = %d\n",
+ __func__, udev, pm_data->name, pm_data->intf_cnt);
+
+ if (!pm_data->intf_cnt) {
+ pr_info("%s: all intf device unregistered from %s\n",
+ __func__, pm_data->name);
+ pm_data->udev = NULL;
+ /* force fatal for debug when HSIC disconnect */
+ if (!pm_data->shutdown) {
+ mdm_force_fatal();
+ }
+ }
+}
+
+static void mdm_hsic_rpm_check(struct work_struct *work)
+{
+ struct mdm_hsic_pm_data *pm_data =
+ container_of(work, struct mdm_hsic_pm_data,
+ request_resume_work.work);
+ struct device *dev;
+
+ if (pm_data->shutdown)
+ return;
+
+ pr_info("%s\n", __func__);
+
+ if (!pm_data->udev)
+ return;
+
+ if (lpa_handling) {
+ pr_info("ignore resume req, lpa handling\n");
+ return;
+ }
+
+ dev = &pm_data->udev->dev;
+
+ if (pm_runtime_resume(dev) < 0)
+ queue_delayed_work(pm_data->wq, &pm_data->request_resume_work,
+ msecs_to_jiffies(20));
+
+ if (pm_runtime_suspended(dev))
+ queue_delayed_work(pm_data->wq, &pm_data->request_resume_work,
+ msecs_to_jiffies(20));
+};
+
+static void mdm_hsic_rpm_start(struct work_struct *work)
+{
+ struct mdm_hsic_pm_data *pm_data =
+ container_of(work, struct mdm_hsic_pm_data,
+ auto_rpm_start_work.work);
+ struct usb_device *udev = pm_data->udev;
+ struct device *dev, *pdev, *hdev;
+
+ pr_info("%s\n", __func__);
+
+ if (!pm_data->intf_cnt || !pm_data->udev)
+ return;
+
+ dev = &pm_data->udev->dev;
+ pdev = dev->parent;
+ pm_runtime_set_autosuspend_delay(dev, 500);
+ hdev = udev->bus->root_hub->dev.parent;
+ pr_info("EHCI runtime %s, %s\n", dev_driver_string(hdev),
+ dev_name(hdev));
+
+ pm_runtime_allow(dev);
+ pm_runtime_allow(hdev);/*ehci*/
+
+ pm_data->shutdown = false;
+}
+
+static void mdm_hsic_rpm_restart(struct work_struct *work)
+{
+ struct mdm_hsic_pm_data *pm_data =
+ container_of(work, struct mdm_hsic_pm_data,
+ auto_rpm_restart_work.work);
+ struct device *dev;
+
+ pr_info("%s\n", __func__);
+
+ if (!pm_data->intf_cnt || !pm_data->udev)
+ return;
+
+ dev = &pm_data->udev->dev;
+ pm_runtime_set_autosuspend_delay(dev, 500);
+}
+
+static void fast_dormancy_func(struct work_struct *work)
+{
+ struct mdm_hsic_pm_data *pm_data =
+ container_of(work, struct mdm_hsic_pm_data,
+ fast_dormancy_work.work);
+ pr_debug("%s\n", __func__);
+
+ if (!pm_data || !pm_data->fd_wake_time)
+ return;
+
+ wake_lock_timeout(&pm_data->fd_wake, pm_data->fd_wake_time);
+};
+
+void fast_dormancy_wakelock(const char *name)
+{
+ struct mdm_hsic_pm_data *pm_data = get_pm_data_by_dev_name(name);
+
+ if (!pm_data || !pm_data->fd_wake_time)
+ return;
+
+ queue_delayed_work(pm_data->wq, &pm_data->fast_dormancy_work, 0);
+}
+
+static ssize_t show_waketime(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct mdm_hsic_pm_data *pm_data = platform_get_drvdata(pdev);
+ char *p = buf;
+ unsigned int msec;
+
+ if (!pm_data)
+ return 0;
+
+ msec = jiffies_to_msecs(pm_data->fd_wake_time);
+ p += sprintf(p, "%u\n", msec);
+
+ return p - buf;
+}
+
+static ssize_t store_waketime(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct mdm_hsic_pm_data *pm_data = platform_get_drvdata(pdev);
+ unsigned long msec;
+ int r;
+
+ if (!pm_data)
+ return count;
+
+ r = strict_strtoul(buf, 10, &msec);
+ if (r)
+ return count;
+
+ pm_data->fd_wake_time = msecs_to_jiffies(msec);
+
+ return count;
+}
+static DEVICE_ATTR(waketime, 0660, show_waketime, store_waketime);
+
+static ssize_t store_runtime(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct mdm_hsic_pm_data *pm_data = platform_get_drvdata(pdev);
+ int value;
+
+ if (sscanf(buf, "%d", &value) != 1)
+ return -EINVAL;
+
+ if (!pm_data || !pm_data->intf_cnt || !pm_data->udev)
+ return -ENXIO;
+
+ if (value == 1) {
+ pr_info("%s: request runtime resume\n", __func__);
+ if (pm_request_resume(&pm_data->udev->dev) < 0)
+ pr_err("%s: unable to add pm work for rpm\n", __func__);
+ }
+
+ return count;
+}
+static DEVICE_ATTR(runtime, 0664, NULL, store_runtime);
+
+static struct attribute *mdm_hsic_attrs[] = {
+ &dev_attr_waketime.attr,
+ &dev_attr_runtime.attr,
+ NULL
+};
+
+static struct attribute_group mdm_hsic_attrgroup = {
+ .attrs = mdm_hsic_attrs,
+};
+
+static int mdm_reset_notify_main(struct notifier_block *this,
+ unsigned long event, void *ptr) {
+ pr_info("%s\n", __func__);
+
+ return NOTIFY_DONE;
+};
+
+static struct notifier_block mdm_reset_main_block = {
+ .notifier_call = mdm_reset_notify_main,
+};
+
+static int mdm_hsic_pm_notify_event(struct notifier_block *this,
+ unsigned long event, void *ptr)
+{
+ struct mdm_hsic_pm_data *pm_data =
+ container_of(this, struct mdm_hsic_pm_data, pm_notifier);
+
+ switch (event) {
+ case PM_SUSPEND_PREPARE:
+ /* to catch blocked resume req */
+ pm_data->state_busy = false;
+ pm_data->block_request = true;
+ pr_info("%s: block request\n", __func__);
+ return NOTIFY_OK;
+ case PM_POST_SUSPEND:
+ pm_data->block_request = false;
+ pr_info("%s: unblock request\n", __func__);
+
+ if (pm_data->shutdown) {
+ notify_modem_fatal();
+ return NOTIFY_DONE;
+ }
+ /**
+ * cover L2 -> L3 broken and resume req blocked case :
+ * force resume request for the lost request
+ */
+ /* pm_request_resume(&pm_data->udev->dev); */
+ queue_delayed_work(pm_data->wq, &pm_data->request_resume_work,
+ msecs_to_jiffies(20));
+ /*pm_runtime_set_autosuspend_delay(&pm_data->udev->dev, 200);*/
+ queue_delayed_work(pm_data->wq, &pm_data->auto_rpm_restart_work,
+ msecs_to_jiffies(20));
+
+ request_active_lock_set(pm_data->name);
+
+ return NOTIFY_OK;
+ }
+ return NOTIFY_DONE;
+}
+
+#define HSIC_RESUME_TRIGGER_LEVEL 1
+static irqreturn_t mdm_hsic_irq_handler(int irq, void *data)
+{
+ int irq_level;
+ struct mdm_hsic_pm_data *pm_data = data;
+
+ if (!pm_data || !pm_data->intf_cnt || !pm_data->udev)
+ return IRQ_HANDLED;
+
+ if (pm_data->shutdown)
+ return IRQ_HANDLED;
+
+ /**
+ * host wake up handler, takes both edge
+ * in rising, isr triggers L2 -> L0 resume
+ */
+
+ irq_level = gpio_get_value(pm_data->gpio_host_wake);
+ pr_info("%s: detect %s edge\n", __func__,
+ irq_level ? "Rising" : "Falling");
+
+ if (irq_level != HSIC_RESUME_TRIGGER_LEVEL)
+ return IRQ_HANDLED;
+
+ if (pm_data->block_request) {
+ pr_info("%s: request blocked by kernel suspending\n", __func__);
+ pm_data->state_busy = true;
+ /* for blocked request, set wakelock to return at dpm suspend */
+ wake_lock(&pm_data->l2_wake);
+ return IRQ_HANDLED;
+ }
+#if 0
+ if (pm_request_resume(&pm_data->udev->dev) < 0)
+ pr_err("%s: unable to add pm work for rpm\n", __func__);
+ /* check runtime pm runs in Active state, after 100ms */
+ queue_delayed_work(pm_data->wq, &pm_data->request_resume_work,
+ msecs_to_jiffies(200));
+#else
+ queue_delayed_work(pm_data->wq, &pm_data->request_resume_work, 0);
+#endif
+ return IRQ_HANDLED;
+}
+
+static int mdm_hsic_pm_gpio_init(struct mdm_hsic_pm_data *pm_data,
+ struct platform_device *pdev)
+{
+ int ret;
+ struct resource *res;
+
+ /* get gpio from platform data */
+
+ /* host ready gpio */
+ res = platform_get_resource_byname(pdev, IORESOURCE_IO,
+ "AP2MDM_HSIC_ACTIVE");
+ if (res)
+ pm_data->gpio_host_ready = res->start;
+
+ if (pm_data->gpio_host_ready) {
+ ret = gpio_request(pm_data->gpio_host_ready, "host_rdy");
+ if (ret < 0)
+ return ret;
+ gpio_direction_output(pm_data->gpio_host_ready, 1);
+ } else
+ return -ENXIO;
+
+ /* device ready gpio */
+ res = platform_get_resource_byname(pdev, IORESOURCE_IO,
+ "MDM2AP_DEVICE_PWR_ACTIVE");
+ if (res)
+ pm_data->gpio_device_ready = res->start;
+ if (pm_data->gpio_device_ready) {
+ ret = gpio_request(pm_data->gpio_device_ready, "device_rdy");
+ if (ret < 0)
+ return ret;
+ gpio_direction_input(pm_data->gpio_device_ready);
+ s3c_gpio_cfgpin(pm_data->gpio_device_ready, S3C_GPIO_INPUT);
+ } else
+ return -ENXIO;
+
+ /* host wake gpio */
+ res = platform_get_resource_byname(pdev, IORESOURCE_IO,
+ "MDM2AP_RESUME_REQ");
+ if (res)
+ pm_data->gpio_host_wake = res->start;
+ if (pm_data->gpio_host_wake) {
+ ret = gpio_request(pm_data->gpio_host_wake, "host_wake");
+ if (ret < 0)
+ return ret;
+ gpio_direction_input(pm_data->gpio_host_wake);
+ s3c_gpio_cfgpin(pm_data->gpio_host_wake, S3C_GPIO_SFN(0xF));
+ } else
+ return -ENXIO;
+
+ if (pm_data->gpio_host_wake)
+ pm_data->irq = gpio_to_irq(pm_data->gpio_host_wake);
+
+ if (!pm_data->irq) {
+ pr_err("fail to get host wake irq\n");
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+static void mdm_hsic_pm_gpio_free(struct mdm_hsic_pm_data *pm_data)
+{
+ if (pm_data->gpio_host_ready)
+ gpio_free(pm_data->gpio_host_ready);
+
+ if (pm_data->gpio_device_ready)
+ gpio_free(pm_data->gpio_device_ready);
+
+ if (pm_data->gpio_host_wake)
+ gpio_free(pm_data->gpio_host_wake);
+}
+
+static int link_pm_netdev_event(struct notifier_block *this,
+ unsigned long event, void *ptr)
+{
+ struct mdm_hsic_pm_data *pm_data =
+ container_of(this, struct mdm_hsic_pm_data, netdev_notifier);
+ struct mdm_hsic_pm_platform_data *mdm_pdata = pm_data->mdm_pdata;
+ struct net_device *dev = ptr;
+
+ if (!net_eq(dev_net(dev), &init_net))
+ return NOTIFY_DONE;
+
+ if (!strncmp(dev->name, "rndis", 5)) {
+ switch (event) {
+ case NETDEV_UP:
+ pr_info("%s: %s UP\n", __func__, dev->name);
+ if (mdm_pdata->freq_lock)
+ mdm_pdata->freq_lock(mdm_pdata->dev);
+
+ break;
+ case NETDEV_DOWN:
+ pr_info("%s: %s DOWN\n", __func__, dev->name);
+ if (mdm_pdata->freq_unlock)
+ mdm_pdata->freq_unlock(mdm_pdata->dev);
+ break;
+ }
+ }
+ return NOTIFY_DONE;
+}
+
+static int usb_composite_notifier_event(struct notifier_block *this,
+ unsigned long event, void *ptr)
+{
+ struct mdm_hsic_pm_data *pm_data =
+ container_of(this, struct mdm_hsic_pm_data,
+ usb_composite_notifier);
+ struct mdm_hsic_pm_platform_data *mdm_pdata = pm_data->mdm_pdata;
+
+ switch (event) {
+ case 0:
+ if (mdm_pdata->freq_unlock)
+ mdm_pdata->freq_unlock(mdm_pdata->dev);
+ pr_info("%s: USB detached\n", __func__);
+ break;
+ case 1:
+ if (mdm_pdata->freq_lock)
+ mdm_pdata->freq_lock(mdm_pdata->dev);
+ pr_info("%s: USB attached\n", __func__);
+ break;
+ }
+ pr_info("%s: usb configuration: %s\n", __func__, (char *)ptr);
+
+ return NOTIFY_DONE;
+}
+
+static int mdm_hsic_pm_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct mdm_hsic_pm_data *pm_data;
+
+ pr_info("%s for %s\n", __func__, pdev->name);
+
+ pm_data = kzalloc(sizeof(struct mdm_hsic_pm_data), GFP_KERNEL);
+ if (!pm_data) {
+ pr_err("%s: fail to alloc pm_data\n", __func__);
+ return -ENOMEM;
+ }
+
+ /* initial value */
+ memcpy(pm_data->name, pdev->name, strlen(pdev->name));
+
+ ret = mdm_hsic_pm_gpio_init(pm_data, pdev);
+ if (ret < 0)
+ goto err_gpio_init_fail;
+
+ /* request irq for host wake interrupt */
+ ret = request_irq(pm_data->irq, mdm_hsic_irq_handler,
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_DISABLED,
+ pm_data->name, (void *)pm_data);
+ if (ret < 0) {
+ pr_err("%s: fail to request irq(%d)\n", __func__, ret);
+ goto err_request_irq;
+ }
+
+ ret = enable_irq_wake(pm_data->irq);
+ if (ret < 0) {
+ pr_err("%s: fail to set wake irq(%d)\n", __func__, ret);
+ goto err_set_wake_irq;
+ }
+
+ pm_data->wq = create_singlethread_workqueue("hsicrpmd");
+ if (!pm_data->wq) {
+ pr_err("%s: fail to create wq\n", __func__);
+ goto err_create_wq;
+ }
+
+ if (sysfs_create_group(&pdev->dev.kobj, &mdm_hsic_attrgroup) < 0) {
+ pr_err("%s: fail to create sysfs\n", __func__);
+ goto err_create_sys_file;
+ }
+
+ pm_data->mdm_pdata =
+ (struct mdm_hsic_pm_platform_data *)pdev->dev.platform_data;
+ INIT_DELAYED_WORK(&pm_data->auto_rpm_start_work, mdm_hsic_rpm_start);
+ INIT_DELAYED_WORK(&pm_data->auto_rpm_restart_work,
+ mdm_hsic_rpm_restart);
+ INIT_DELAYED_WORK(&pm_data->request_resume_work, mdm_hsic_rpm_check);
+ INIT_DELAYED_WORK(&pm_data->fast_dormancy_work, fast_dormancy_func);
+ /* register notifier call */
+ pm_data->pm_notifier.notifier_call = mdm_hsic_pm_notify_event;
+ register_pm_notifier(&pm_data->pm_notifier);
+ blocking_notifier_chain_register(&mdm_reset_notifier_list,
+ &mdm_reset_main_block);
+
+ pm_data->netdev_notifier.notifier_call = link_pm_netdev_event;
+ register_netdevice_notifier(&pm_data->netdev_notifier);
+
+ pm_data->usb_composite_notifier.notifier_call =
+ usb_composite_notifier_event;
+ register_usb_composite_notifier(&pm_data->usb_composite_notifier);
+
+ wake_lock_init(&pm_data->l2_wake, WAKE_LOCK_SUSPEND, pm_data->name);
+ wake_lock_init(&pm_data->boot_wake, WAKE_LOCK_SUSPEND, "mdm_boot");
+ wake_lock_init(&pm_data->fd_wake, WAKE_LOCK_SUSPEND, "fast_dormancy");
+ pm_data->fd_wake_time = DEFAULT_RAW_WAKE_TIME;
+
+ print_pm_dev_info(pm_data);
+ list_add(&pm_data->list, &hsic_pm_dev_list);
+ platform_set_drvdata(pdev, pm_data);
+ pr_info("%s for %s has done\n", __func__, pdev->name);
+
+ return 0;
+
+err_create_sys_file:
+ destroy_workqueue(pm_data->wq);
+err_create_wq:
+ disable_irq_wake(pm_data->irq);
+err_set_wake_irq:
+ free_irq(pm_data->irq, (void *)pm_data);
+err_request_irq:
+err_gpio_init_fail:
+ mdm_hsic_pm_gpio_free(pm_data);
+ kfree(pm_data);
+ return -ENXIO;
+}
+
+static struct platform_driver mdm_pm_driver = {
+ .probe = mdm_hsic_pm_probe,
+ .driver = {
+ .name = "mdm_hsic_pm0",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init mdm_hsic_pm_init(void)
+{
+ /* in lpm mode, do not load modem driver */
+ if (lpcharge)
+ return 0;
+ return platform_driver_register(&mdm_pm_driver);
+}
+
+static void __exit mdm_hsic_pm_exit(void)
+{
+ platform_driver_unregister(&mdm_pm_driver);
+}
+
+late_initcall(mdm_hsic_pm_init);