diff options
author | Steve Lin <stlin@nvidia.com> | 2011-08-29 15:40:11 -0700 |
---|---|---|
committer | Varun Colbert <vcolbert@nvidia.com> | 2011-09-06 14:47:39 -0700 |
commit | f737bc30ee9509a79e499c975b61c5f58bb19bb3 (patch) | |
tree | 34562a321c4b02be7aed0bc974489b1613ed2a61 | |
parent | 9f2693a80274bcd9eb8e7424bca87f34cc190741 (diff) |
arm: tegra: baseband: add USB modem power management support
This platform driver enables the generic USB modem power management support
for out-of_band remote wakeup, selective suspend and system suspend/resume.
Bug 854339
Change-Id: I6cc42dedf4031399691c70388fce4e69ec4b881f
Reviewed-on: http://git-master/r/44911
Reviewed-by: Varun Colbert <vcolbert@nvidia.com>
Tested-by: Varun Colbert <vcolbert@nvidia.com>
-rw-r--r-- | arch/arm/mach-tegra/Kconfig | 7 | ||||
-rw-r--r-- | arch/arm/mach-tegra/Makefile | 1 | ||||
-rw-r--r-- | arch/arm/mach-tegra/include/mach/tegra_usb_modem_power.h | 47 | ||||
-rw-r--r-- | arch/arm/mach-tegra/tegra_usb_modem_power.c | 290 |
4 files changed, 345 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/Kconfig b/arch/arm/mach-tegra/Kconfig index 78168f8f3768..bd1746509a3e 100644 --- a/arch/arm/mach-tegra/Kconfig +++ b/arch/arm/mach-tegra/Kconfig @@ -305,3 +305,10 @@ config TEGRA_EDP_EXACT_FREQ When enabled the cpu will run at the exact frequency specified in the EDP table when EDP capping is applied; when disabled the next lower cpufreq frequency will be used. + +config TEGRA_USB_MODEM_POWER + bool "Enable tegra usb modem power management" + default n + help + This option enables support for out-of_band remote wakeup, selective + suspend and system suspend/resume. diff --git a/arch/arm/mach-tegra/Makefile b/arch/arm/mach-tegra/Makefile index 7804f37fa15b..8ba2631112d8 100644 --- a/arch/arm/mach-tegra/Makefile +++ b/arch/arm/mach-tegra/Makefile @@ -99,6 +99,7 @@ obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += tegra3_mc.o endif obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += tegra3_tsensor.o obj-$(CONFIG_TEGRA_DYNAMIC_PWRDET) += powerdetect.o +obj-$(CONFIG_TEGRA_USB_MODEM_POWER) += tegra_usb_modem_power.o obj-${CONFIG_MACH_HARMONY} += board-harmony.o obj-${CONFIG_MACH_HARMONY} += board-harmony-pinmux.o diff --git a/arch/arm/mach-tegra/include/mach/tegra_usb_modem_power.h b/arch/arm/mach-tegra/include/mach/tegra_usb_modem_power.h new file mode 100644 index 000000000000..0ce7fa40eb2e --- /dev/null +++ b/arch/arm/mach-tegra/include/mach/tegra_usb_modem_power.h @@ -0,0 +1,47 @@ +/* + * arch/arm/mach-tegra/include/mach/tegra_usb_modem_power.c + * + * Copyright (c) 2011, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __MACH_TEGRA_USB_MODEM_POWER_H +#define __MACH_TEGRA_USB_MODEM_POWER_H + +#include <linux/interrupt.h> + +/* modem capabilities */ +#define TEGRA_MODEM_AUTOSUSPEND 0x01 +#define TEGRA_MODEM_RECOVERY 0x02 + +/* modem operations */ +struct tegra_modem_operations { + int (*init) (void); /* modem init */ + void (*start) (void); /* modem start */ + void (*stop) (void); /* modem stop */ + void (*suspend) (void); /* send L3 hint during system suspend */ + void (*resume) (void); /* send L3->0 hint during system resume */ + void (*reset) (void); /* modem reset */ +}; + +/* tegra usb modem power platform data */ +struct tegra_usb_modem_power_platform_data { + const struct tegra_modem_operations *ops; + unsigned int wake_gpio; /* remote wakeup gpio */ + unsigned int flags; /* remote wakeup irq flags */ +}; + +#endif /* __MACH_TEGRA_USB_MODEM_POWER_H */ diff --git a/arch/arm/mach-tegra/tegra_usb_modem_power.c b/arch/arm/mach-tegra/tegra_usb_modem_power.c new file mode 100644 index 000000000000..b4ec033ad612 --- /dev/null +++ b/arch/arm/mach-tegra/tegra_usb_modem_power.c @@ -0,0 +1,290 @@ +/* + * arch/arm/mach-tegra/tegra_usb_modem_power.c + * + * Copyright (c) 2011, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/workqueue.h> +#include <linux/gpio.h> +#include <linux/usb.h> +#include <linux/err.h> +#include <linux/wakelock.h> +#include <mach/tegra_usb_modem_power.h> + +struct tegra_usb_modem { + unsigned int wake_gpio; /* remote wakeup gpio */ + unsigned int wake_cnt; /* remote wakeup counter */ + int irq; /* remote wakeup irq */ + struct mutex lock; + struct wake_lock wake_lock; /* modem wake lock */ + unsigned int vid; /* modem vendor id */ + unsigned int pid; /* modem product id */ + struct usb_device *udev; /* modem usb device */ + struct usb_interface *intf; /* first modem usb interface */ + struct workqueue_struct *wq; /* modem workqueue */ + struct delayed_work recovery_work; /* modem recovery work */ + const struct tegra_modem_operations *ops; /* modem operations */ + unsigned int capability; /* modem capability */ +}; + +static struct tegra_usb_modem tegra_mdm; + +/* supported modems */ +static const struct usb_device_id modem_list[] = { + {USB_DEVICE(0x1983, 0x0310), /* Icera 450 Modem */ + .driver_info = 0, /* enable autosuspend with TEGRA_MODEM_AUTOSUSPEND */ + }, + {USB_DEVICE(0x1983, 0x0321), /* Icera 450 Modem */ + .driver_info = 0, /* enable autosuspend with TEGRA_MODEM_AUTOSUSPEND */ + }, + {} +}; + +static irqreturn_t tegra_usb_modem_wake_thread(int irq, void *data) +{ + struct tegra_usb_modem *modem = (struct tegra_usb_modem *)data; + + wake_lock_timeout(&modem->wake_lock, HZ); + mutex_lock(&modem->lock); + if (modem->udev) { + usb_lock_device(modem->udev); + pr_info("modem wake (%u)\n", ++(modem->wake_cnt)); + if (usb_autopm_get_interface(modem->intf) == 0) + usb_autopm_put_interface_async(modem->intf); + usb_unlock_device(modem->udev); + } + mutex_unlock(&modem->lock); + + return IRQ_HANDLED; +} + +static void tegra_usb_modem_recovery(struct work_struct *ws) +{ + struct tegra_usb_modem *modem = container_of(ws, struct tegra_usb_modem, + recovery_work.work); + + mutex_lock(&modem->lock); + if (!modem->udev) { /* assume modem crashed */ + if (modem->ops && modem->ops->reset) + modem->ops->reset(); + } + mutex_unlock(&modem->lock); +} + +static void device_add_handler(struct usb_device *udev) +{ + const struct usb_device_descriptor *desc = &udev->descriptor; + struct usb_interface *intf = usb_ifnum_to_if(udev, 0); + const struct usb_device_id *id = usb_match_id(intf, modem_list); + + if (id) { + pr_info("Add device %d <%s %s>\n", udev->devnum, + udev->manufacturer, udev->product); + + mutex_lock(&tegra_mdm.lock); + tegra_mdm.udev = udev; + tegra_mdm.intf = intf; + tegra_mdm.vid = desc->idVendor; + tegra_mdm.pid = desc->idProduct; + tegra_mdm.wake_cnt = 0; + tegra_mdm.capability = id->driver_info; + mutex_unlock(&tegra_mdm.lock); + + pr_info("persist_enabled: %u\n", udev->persist_enabled); + + if (tegra_mdm.capability & TEGRA_MODEM_AUTOSUSPEND) { + usb_enable_autosuspend(udev); + pr_info("enable autosuspend for %s %s\n", + udev->manufacturer, udev->product); + } + } +} + +static void device_remove_handler(struct usb_device *udev) +{ + const struct usb_device_descriptor *desc = &udev->descriptor; + + if (desc->idVendor == tegra_mdm.vid && + desc->idProduct == tegra_mdm.pid) { + pr_info("Remove device %d <%s %s>\n", udev->devnum, + udev->manufacturer, udev->product); + + mutex_lock(&tegra_mdm.lock); + tegra_mdm.udev = NULL; + tegra_mdm.intf = NULL; + tegra_mdm.vid = 0; + mutex_unlock(&tegra_mdm.lock); + + if (tegra_mdm.capability & TEGRA_MODEM_RECOVERY) + queue_delayed_work(tegra_mdm.wq, + &tegra_mdm.recovery_work, HZ * 10); + } +} + +static int usb_notify(struct notifier_block *self, unsigned long action, + void *blob) +{ + switch (action) { + case USB_DEVICE_ADD: + device_add_handler(blob); + break; + case USB_DEVICE_REMOVE: + device_remove_handler(blob); + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block usb_nb = { + .notifier_call = usb_notify, +}; + +static int tegra_usb_modem_probe(struct platform_device *pdev) +{ + struct tegra_usb_modem_power_platform_data *pdata = + pdev->dev.platform_data; + int ret; + + if (!pdata) { + dev_dbg(&pdev->dev, "platform_data not available\n"); + return -EINVAL; + } + + /* get modem operations from platform data */ + tegra_mdm.ops = (const struct tegra_modem_operations *)pdata->ops; + + if (tegra_mdm.ops) { + /* modem init */ + if (tegra_mdm.ops->init) { + ret = tegra_mdm.ops->init(); + if (ret) + return ret; + } + + /* start modem */ + if (tegra_mdm.ops->start) + tegra_mdm.ops->start(); + } + + mutex_init(&(tegra_mdm.lock)); + wake_lock_init(&(tegra_mdm.wake_lock), WAKE_LOCK_SUSPEND, + "tegra_usb_mdm_lock"); + + /* create work queue */ + tegra_mdm.wq = create_workqueue("tegra_usb_mdm_queue"); + INIT_DELAYED_WORK(&(tegra_mdm.recovery_work), tegra_usb_modem_recovery); + + /* create threaded irq for remote wakeup */ + if (pdata->wake_gpio) { + /* get remote wakeup gpio from platform data */ + tegra_mdm.wake_gpio = pdata->wake_gpio; + + ret = gpio_request(tegra_mdm.wake_gpio, "usb_mdm_wake"); + if (ret) + return ret; + + tegra_gpio_enable(tegra_mdm.wake_gpio); + + /* enable IRQ for remote wakeup */ + tegra_mdm.irq = gpio_to_irq(tegra_mdm.wake_gpio); + + ret = + request_threaded_irq(tegra_mdm.irq, NULL, + tegra_usb_modem_wake_thread, + pdata->flags, "tegra_usb_mdm_wake", + &tegra_mdm); + if (ret < 0) { + dev_err(&pdev->dev, "%s: request_threaded_irq error\n", + __func__); + return ret; + } + + ret = enable_irq_wake(tegra_mdm.irq); + if (ret) { + dev_err(&pdev->dev, "%s: enable_irq_wake error\n", + __func__); + free_irq(tegra_mdm.irq, &tegra_mdm); + return ret; + } + } + + usb_register_notify(&usb_nb); + dev_info(&pdev->dev, "Initialized tegra_usb_modem_power\n"); + + return 0; +} + +static int __exit tegra_usb_modem_remove(struct platform_device *pdev) +{ + usb_unregister_notify(&usb_nb); + free_irq(tegra_mdm.irq, &tegra_mdm); + return 0; +} + +#ifdef CONFIG_PM +static int tegra_usb_modem_suspend(struct platform_device *pdev, + pm_message_t state) +{ + /* send L3 hint to modem */ + if (tegra_mdm.ops && tegra_mdm.ops->suspend) + tegra_mdm.ops->suspend(); + return 0; +} + +static int tegra_usb_modem_resume(struct platform_device *pdev) +{ + /* send L3->L0 hint to modem */ + if (tegra_mdm.ops && tegra_mdm.ops->resume) + tegra_mdm.ops->resume(); + return 0; +} +#endif + +static struct platform_driver tegra_usb_modem_power_driver = { + .driver = { + .name = "tegra_usb_modem_power", + .owner = THIS_MODULE, + }, + .probe = tegra_usb_modem_probe, + .remove = __exit_p(tegra_usb_modem_remove), +#ifdef CONFIG_PM + .suspend = tegra_usb_modem_suspend, + .resume = tegra_usb_modem_resume, +#endif +}; + +static int __init tegra_usb_modem_power_init(void) +{ + return platform_driver_register(&tegra_usb_modem_power_driver); +} + +subsys_initcall(tegra_usb_modem_power_init); + +static void __exit tegra_usb_modem_power_exit(void) +{ + platform_driver_unregister(&tegra_usb_modem_power_driver); +} + +module_exit(tegra_usb_modem_power_exit); + +MODULE_DESCRIPTION("Tegra usb modem power management driver"); +MODULE_LICENSE("GPL"); |