diff options
author | Steve Lin <stlin@nvidia.com> | 2011-07-05 15:22:25 -0700 |
---|---|---|
committer | Dan Willemsen <dwillemsen@nvidia.com> | 2011-11-30 21:47:50 -0800 |
commit | 644dea2a7eaa3e2c19f7ad65a113607b2c9aeb89 (patch) | |
tree | caf12183385eff7be9a19fce4616cf456d58a178 | |
parent | 3fcd68e50297e8099007ca4a93c4d6fecb832955 (diff) |
ARM: tegra: baseband: modem flashless boot and remote wakeup
Support modem reboot and re-enumeration.
Support modem remote wakeup.
Bug 814261
Bug 814271
Bug 846135
Original-Change-Id: I103722d0248bcb1565d5f5799a2e4317c2579a95
Reviewed-on: http://git-master/r/31441
Tested-by: Szming Lin <stlin@nvidia.com>
Reviewed-by: Udaykumar Rameshchan Raval <uraval@nvidia.com>
Reviewed-by: Scott Williams <scwilliams@nvidia.com>
Rebase-Id: Rd1a0b91d4708fc039648df0cd491c9a382b5fcb2
-rw-r--r-- | arch/arm/mach-tegra/board-enterprise-baseband.c | 277 |
1 files changed, 238 insertions, 39 deletions
diff --git a/arch/arm/mach-tegra/board-enterprise-baseband.c b/arch/arm/mach-tegra/board-enterprise-baseband.c index 285f01dcdb24..eefb230599ff 100644 --- a/arch/arm/mach-tegra/board-enterprise-baseband.c +++ b/arch/arm/mach-tegra/board-enterprise-baseband.c @@ -22,7 +22,11 @@ #include <linux/platform_device.h> #include <linux/delay.h> #include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/irq.h> #include <linux/err.h> +#include <linux/device.h> +#include <linux/usb.h> #include <linux/platform_data/tegra_usb.h> #include <asm/mach-types.h> #include <asm/mach/arch.h> @@ -31,24 +35,50 @@ #include "devices.h" #include "gpio-names.h" -/* T30 BB GPIO */ -#define MODEM_PWR_ON TEGRA_GPIO_PE0 -#define MODEM_RESET TEGRA_GPIO_PE1 +#define ENABLE_AUTO_SUSPEND 0 +#define ENABLE_HOST_RECOVERY 0 /* flashless only feature */ -/* PH450 */ -#define AP2MDM_ACK TEGRA_GPIO_PE3 -#define MDM2AP_ACK TEGRA_GPIO_PU5 -#define AP2MDM_ACK2 TEGRA_GPIO_PE2 -#define MDM2AP_ACK2 TEGRA_GPIO_PV0 +/* Tegra3 BB GPIO */ +#define MODEM_PWR_ON TEGRA_GPIO_PE0 +#define MODEM_RESET TEGRA_GPIO_PE1 +#define BB_RST_OUT TEGRA_GPIO_PV1 + +/* PH450 GPIO */ +#define AP2MDM_ACK TEGRA_GPIO_PE3 +#define MDM2AP_ACK TEGRA_GPIO_PU5 +#define AP2MDM_ACK2 TEGRA_GPIO_PE2 +#define MDM2AP_ACK2 TEGRA_GPIO_PV0 + +struct ph450_priv { + unsigned int wake_gpio; + unsigned int wake_cnt; + unsigned int restart_gpio; + struct mutex lock; + unsigned int vid; + unsigned int pid; + struct usb_device *udev; + struct usb_interface *intf; + struct workqueue_struct *wq; + struct delayed_work reset_work; +}; + +static struct usb_device_id modem_list[] = { + { + .match_flags = USB_DEVICE_ID_MATCH_VENDOR, + .idVendor = 0x1983, + }, + {}, +}; static int ph450_reset(void); static int ph450_handshake(void); +static struct ph450_priv ph450_priv; static struct tegra_ulpi_trimmer e1219_trimmer = { 10, 1, 1, 1 }; static struct tegra_ulpi_config ehci2_null_ulpi_phy_config = { .trimmer = &e1219_trimmer, - .preinit = ph450_reset, + .preinit = NULL, .postinit = ph450_handshake, }; @@ -66,8 +96,147 @@ static int __init tegra_null_ulpi_init(void) return 0; } +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); + + if (usb_match_id(intf, modem_list)) { + pr_info("Add device %d <%s %s>\n", udev->devnum, + udev->manufacturer, udev->product); + + mutex_lock(&ph450_priv.lock); + ph450_priv.udev = udev; + ph450_priv.intf = intf; + ph450_priv.vid = desc->idVendor; + ph450_priv.pid = desc->idProduct; + ph450_priv.wake_cnt = 0; + mutex_unlock(&ph450_priv.lock); + + pr_info("persist_enabled: %u\n", udev->persist_enabled); + +#if ENABLE_AUTO_SUSPEND + usb_enable_autosuspend(udev); + pr_info("enable autosuspend for %s %s\n", udev->manufacturer, + udev->product); +#endif + } +} + +static void device_remove_handler(struct usb_device *udev) +{ + const struct usb_device_descriptor *desc = &udev->descriptor; + + if (desc->idVendor == ph450_priv.vid + && desc->idProduct == ph450_priv.pid) { + pr_info("Remove device %d <%s %s>\n", udev->devnum, + udev->manufacturer, udev->product); + + mutex_lock(&ph450_priv.lock); + ph450_priv.udev = NULL; + ph450_priv.intf = NULL; + ph450_priv.vid = 0; + mutex_unlock(&ph450_priv.lock); + +#if ENABLE_HOST_RECOVERY + queue_delayed_work(ph450_priv.wq, &ph450_priv.reset_work, + HZ * 10); +#endif + } +} + +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 irqreturn_t mdm_start_thread(int irq, void *data) +{ + struct ph450_priv *priv = (struct ph450_priv *)data; + + if (gpio_get_value(priv->restart_gpio)) { + pr_info("BB_RST_OUT high\n"); + gpio_set_value(AP2MDM_ACK2, 1); + } else { + pr_info("BB_RST_OUT low\n"); + gpio_set_value(AP2MDM_ACK2, 0); + } + + return IRQ_HANDLED; +} + +static irqreturn_t mdm_wake_thread(int irq, void *data) +{ + struct ph450_priv *priv = (struct ph450_priv *)data; + + if (gpio_get_value(priv->wake_gpio) == 0) { + pr_info("MDM2AP_ACK2 low\n"); + + mutex_lock(&priv->lock); + if (priv->udev) { + usb_lock_device(priv->udev); + pr_info("mdm wake (%u)\n", ++(priv->wake_cnt)); + if (usb_autopm_get_interface(priv->intf) == 0) + usb_autopm_put_interface_async(priv->intf); + usb_unlock_device(priv->udev); + } + mutex_unlock(&priv->lock); + } + + return IRQ_HANDLED; +} + +static int ph450_reset(void) +{ + pr_info("modem reset\n"); + + gpio_set_value(AP2MDM_ACK2, 1); + gpio_set_value(MODEM_PWR_ON, 0); + gpio_set_value(MODEM_RESET, 0); + mdelay(200); + gpio_set_value(MODEM_RESET, 1); + mdelay(30); + gpio_set_value(MODEM_PWR_ON, 1); + + return 0; +} + +static int ph450_handshake(void) +{ + /* set AP2MDM_ACK2 low */ + gpio_set_value(AP2MDM_ACK2, 0); + + return 0; +} + +static void ph450_reset_work_handler(struct work_struct *ws) +{ + struct ph450_priv *priv = container_of(ws, struct ph450_priv, + reset_work.work); + + mutex_lock(&priv->lock); + if (!priv->udev) /* assume modem crashed */ + ph450_reset(); + mutex_unlock(&priv->lock); +} + static int __init ph450_init(void) { + int irq; int ret; ret = gpio_request(MODEM_PWR_ON, "mdm_power"); @@ -93,54 +262,83 @@ static int __init ph450_init(void) return ret; } - /* enable pull-up for MDM2AP_ACK2 */ + ret = gpio_request(BB_RST_OUT, "bb_rst_out"); + if (ret) { + gpio_free(MODEM_PWR_ON); + gpio_free(MODEM_RESET); + gpio_free(AP2MDM_ACK2); + gpio_free(MDM2AP_ACK2); + return ret; + } + + /* enable pull-up for MDM2AP_ACK2 and BB_RST_OUT */ tegra_pinmux_set_pullupdown(TEGRA_PINGROUP_GPIO_PV0, TEGRA_PUPD_PULL_UP); + tegra_pinmux_set_pullupdown(TEGRA_PINGROUP_GPIO_PV1, + TEGRA_PUPD_PULL_UP); + tegra_gpio_enable(MODEM_PWR_ON); tegra_gpio_enable(MODEM_RESET); tegra_gpio_enable(AP2MDM_ACK2); tegra_gpio_enable(MDM2AP_ACK2); + tegra_gpio_enable(BB_RST_OUT); gpio_direction_output(MODEM_PWR_ON, 0); gpio_direction_output(MODEM_RESET, 0); gpio_direction_output(AP2MDM_ACK2, 1); gpio_direction_input(MDM2AP_ACK2); + gpio_direction_input(BB_RST_OUT); - return 0; -} + /* reset modem */ + ph450_reset(); -static int ph450_reset(void) -{ - int retry = 100; /* retry for 10 sec */ + ph450_priv.wake_gpio = TEGRA_GPIO_PV0; + ph450_priv.restart_gpio = TEGRA_GPIO_PV1; - gpio_set_value(AP2MDM_ACK2, 1); - gpio_set_value(MODEM_PWR_ON, 0); - gpio_set_value(MODEM_RESET, 0); - mdelay(200); - gpio_set_value(MODEM_RESET, 1); - mdelay(30); - gpio_set_value(MODEM_PWR_ON, 1); + mutex_init(&(ph450_priv.lock)); - while (retry) { - /* wait for MDM2AP_ACK2 low */ - int val = gpio_get_value(MDM2AP_ACK2); - if (!val) { - pr_info("MDM2AP_ACK2 detected\n"); - return 0; - } else { - pr_info("."); - retry--; - mdelay(100); - } + /* create work queue */ + ph450_priv.wq = create_workqueue("mdm_queue"); + INIT_DELAYED_WORK(&(ph450_priv.reset_work), ph450_reset_work_handler); + + usb_register_notify(&usb_nb); + + /* enable IRQ for BB_RST_OUT */ + irq = gpio_to_irq(TEGRA_GPIO_PV1); + + ret = request_threaded_irq(irq, NULL, mdm_start_thread, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "mdm_start", &ph450_priv); + if (ret < 0) { + pr_err("%s: request_threaded_irq error\n", __func__); + return ret; } - return 1; -} -static int ph450_handshake(void) -{ - /* set AP2MDM_ACK2 low */ - gpio_set_value(AP2MDM_ACK2, 0); + ret = enable_irq_wake(irq); + if (ret) { + pr_err("%s: enable_irq_wake error\n", __func__); + free_irq(irq, &ph450_priv); + return ret; + } + + /* enable IRQ for MDM2AP_ACK2 */ + irq = gpio_to_irq(TEGRA_GPIO_PV0); + + ret = request_threaded_irq(irq, NULL, mdm_wake_thread, + IRQF_TRIGGER_FALLING, "mdm_wake", + &ph450_priv); + if (ret < 0) { + pr_err("%s: request_threaded_irq error\n", __func__); + return ret; + } + + ret = enable_irq_wake(irq); + if (ret) { + pr_err("%s: enable_irq_wake error\n", __func__); + free_irq(irq, &ph450_priv); + return ret; + } return 0; } @@ -156,5 +354,6 @@ int __init enterprise_modem_init(void) } tegra_null_ulpi_init(); + return 0; } |