diff options
-rw-r--r-- | arch/arm/mach-tegra/board-whistler-baseband.c | 264 | ||||
-rw-r--r-- | arch/arm/mach-tegra/board-whistler-baseband.h | 31 | ||||
-rw-r--r-- | arch/arm/mach-tegra/include/mach/usb_phy.h | 4 | ||||
-rw-r--r-- | arch/arm/mach-tegra/usb_phy.c | 31 |
4 files changed, 292 insertions, 38 deletions
diff --git a/arch/arm/mach-tegra/board-whistler-baseband.c b/arch/arm/mach-tegra/board-whistler-baseband.c index bb974d99cedf..456c454c8911 100644 --- a/arch/arm/mach-tegra/board-whistler-baseband.c +++ b/arch/arm/mach-tegra/board-whistler-baseband.c @@ -187,8 +187,12 @@ int whistler_baseband_init(void) static int rainbow_570_reset(void); static int rainbow_570_handshake(void); -static int ph450_reset(void); +static int ph450_power_off(void); static int ph450_handshake(void); +static int ph450_phy_on(void); +static int ph450_phy_off(void); + +static struct ph450_priv ph450_priv; static __initdata struct tegra_pingroup_config whistler_null_ulpi_pinmux[] = { {TEGRA_PINGROUP_UAA, TEGRA_MUX_ULPI, TEGRA_PUPD_NORMAL, @@ -199,6 +203,8 @@ static __initdata struct tegra_pingroup_config whistler_null_ulpi_pinmux[] = { TEGRA_TRI_NORMAL}, {TEGRA_PINGROUP_UAC, TEGRA_MUX_RSVD4, TEGRA_PUPD_NORMAL, TEGRA_TRI_NORMAL}, + {TEGRA_PINGROUP_SDIO1, TEGRA_MUX_UARTA, TEGRA_PUPD_PULL_UP, + TEGRA_TRI_NORMAL}, }; static struct tegra_ulpi_trimmer e951_trimmer = { 10, 1, 1, 1 }; @@ -206,8 +212,10 @@ static struct tegra_ulpi_trimmer e951_trimmer = { 10, 1, 1, 1 }; static struct tegra_ulpi_config ehci2_null_ulpi_phy_config = { .inf_type = TEGRA_USB_NULL_ULPI, .trimmer = &e951_trimmer, - .preinit = ph450_reset, + .preinit = NULL, .postinit = ph450_handshake, + .post_phy_on = ph450_phy_on, + .pre_phy_off = ph450_phy_off, }; static struct tegra_ehci_platform_data ehci2_null_ulpi_platform_data = { @@ -216,6 +224,22 @@ static struct tegra_ehci_platform_data ehci2_null_ulpi_platform_data = { .phy_config = &ehci2_null_ulpi_phy_config, }; +static int ph450_phy_on(void) +{ + /* set AP2MDM_ACK2 low */ + gpio_set_value(AP2MDM_ACK2, 0); + pr_info("%s\n", __func__); + return 0; +} + +static int ph450_phy_off(void) +{ + /* set AP2MDM_ACK2 high */ + gpio_set_value(AP2MDM_ACK2, 1); + pr_info("%s\n", __func__); + return 0; +} + static int __init tegra_null_ulpi_init(void) { tegra_ehci2_device.dev.platform_data = &ehci2_null_ulpi_platform_data; @@ -291,8 +315,155 @@ static int rainbow_570_handshake(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.power_cycle_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); + /* hold wait lock to complete the enumeration */ + wake_lock_timeout(&priv->wake_lock, HZ * 2); + } 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"); + + wake_lock_timeout(&priv->wake_lock, HZ); + 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_power_off(void) +{ + pr_info("modem power off\n"); + + gpio_set_value(AP2MDM_ACK2, 1); + gpio_set_value(MODEM_PWR_ON, 0); + return 0; +} + +static int ph450_power_cycle(void) +{ + pr_info("modem power cycle\n"); + mdelay(200); + gpio_set_value(AP2MDM_ACK2, 1); + gpio_set_value(MODEM_PWR_ON, 0); + msleep(100); + 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_power_cycle_work_handler(struct work_struct *ws) +{ + struct ph450_priv *priv = container_of(ws, struct ph450_priv, + power_cycle_work.work); + + mutex_lock(&priv->lock); + if (!priv->udev) /* assume modem crashed */ + ph450_power_cycle(); + mutex_unlock(&priv->lock); +} + static int __init ph450_init(void) { + int irq; int ret; ret = gpio_request(MODEM_PWR_ON, "mdm_power"); @@ -318,40 +489,85 @@ static int __init ph450_init(void) return ret; } - /* enable pull-up for MDM2AP_ACK2 */ - tegra_pinmux_set_pullupdown(TEGRA_PINGROUP_UAC, TEGRA_PUPD_PULL_UP); + 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 BB_RST_OUT */ + tegra_pinmux_set_pullupdown(TEGRA_PINGROUP_UAC, + TEGRA_PUPD_PULL_UP); + tegra_pinmux_set_pullupdown(TEGRA_PINGROUP_GPU, + TEGRA_PUPD_PULL_UP); - tegra_gpio_enable(MODEM_PWR_ON); + tegra_gpio_enable(MODEM_PWR_ON); + gpio_export(MODEM_PWR_ON, false); 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_input(MODEM_RESET); gpio_direction_output(AP2MDM_ACK2, 1); gpio_direction_input(MDM2AP_ACK2); + gpio_direction_input(BB_RST_OUT); - return 0; -} + /* power off modem */ + ph450_power_off(); -static int ph450_reset(void) -{ - gpio_set_value(AP2MDM_ACK2, 1); - gpio_set_value(MODEM_PWR_ON, 0); - gpio_set_value(MODEM_PWR_ON, 1); - mdelay(30); - gpio_set_value(MODEM_RESET, 0); - mdelay(200); - gpio_set_value(MODEM_RESET, 1); - mdelay(30); + ph450_priv.wake_gpio = AP2MDM_ACK2; + ph450_priv.restart_gpio = BB_RST_OUT; - return 1; -} + mutex_init(&(ph450_priv.lock)); + wake_lock_init(&(ph450_priv.wake_lock), WAKE_LOCK_SUSPEND, + "mdm_wake_lock"); -static int ph450_handshake(void) -{ - /* set AP2MDM_ACK2 low */ - gpio_set_value(AP2MDM_ACK2, 0); + /* create work queue */ + ph450_priv.wq = create_workqueue("mdm_queue"); + INIT_DELAYED_WORK(&(ph450_priv.power_cycle_work), ph450_power_cycle_work_handler); + + usb_register_notify(&usb_nb); + + /* enable IRQ for BB_RST_OUT */ + irq = gpio_to_irq(BB_RST_OUT); + + 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; + } + + 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(MDM2AP_ACK2); + + 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; } diff --git a/arch/arm/mach-tegra/board-whistler-baseband.h b/arch/arm/mach-tegra/board-whistler-baseband.h index 6336ed77d8d4..a7fc4e788cf5 100644 --- a/arch/arm/mach-tegra/board-whistler-baseband.h +++ b/arch/arm/mach-tegra/board-whistler-baseband.h @@ -25,10 +25,14 @@ #include <linux/resource.h> #include <linux/platform_device.h> #include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/device.h> +#include <linux/usb.h> +#include <linux/wakelock.h> #include <asm/mach-types.h> #include <mach/pinmux.h> #include <mach/spi.h> - #include "clock.h" #include "devices.h" #include "gpio-names.h" @@ -48,6 +52,9 @@ #define MODEM_PWR_ON TEGRA_GPIO_PV1 #define MODEM_RESET TEGRA_GPIO_PV0 +#define ENABLE_AUTO_SUSPEND 0 +#define ENABLE_HOST_RECOVERY 0 /* flashless only feature */ + /* Rainbow1 and 570 */ #define AWR TEGRA_GPIO_PZ0 #define CWR TEGRA_GPIO_PY6 @@ -57,7 +64,29 @@ /* PH450 */ #define AP2MDM_ACK2 TEGRA_GPIO_PU2 #define MDM2AP_ACK2 TEGRA_GPIO_PV2 +#define BB_RST_OUT TEGRA_GPIO_PV3 +struct ph450_priv { +unsigned int wake_gpio; + unsigned int wake_cnt; + unsigned int restart_gpio; + struct mutex lock; + struct wake_lock wake_lock; + unsigned int vid; + unsigned int pid; + struct usb_device *udev; + struct usb_interface *intf; + struct workqueue_struct *wq; + struct delayed_work power_cycle_work; +}; + +static struct usb_device_id modem_list[] = { + { + .match_flags = USB_DEVICE_ID_MATCH_VENDOR, + .idVendor = 0x1983, + }, + {}, +}; struct whistler_baseband { struct tegra_clk_init_table *clk_init; diff --git a/arch/arm/mach-tegra/include/mach/usb_phy.h b/arch/arm/mach-tegra/include/mach/usb_phy.h index 609134c4ff82..7f127fc70402 100644 --- a/arch/arm/mach-tegra/include/mach/usb_phy.h +++ b/arch/arm/mach-tegra/include/mach/usb_phy.h @@ -52,6 +52,10 @@ struct tegra_ulpi_config { const struct tegra_ulpi_trimmer *trimmer; int (*preinit)(void); int (*postinit)(void); + int (*pre_phy_on)(void); + int (*post_phy_on)(void); + int (*pre_phy_off)(void); + int (*post_phy_off)(void); }; struct tegra_uhsic_config { diff --git a/arch/arm/mach-tegra/usb_phy.c b/arch/arm/mach-tegra/usb_phy.c index 0dc14abc3fa4..081b23073937 100644 --- a/arch/arm/mach-tegra/usb_phy.c +++ b/arch/arm/mach-tegra/usb_phy.c @@ -393,17 +393,14 @@ static void utmip_pad_power_on(struct tegra_usb_phy *phy) void __iomem *base = phy->pad_regs; clk_enable(phy->pad_clk); - spin_lock_irqsave(&utmip_pad_lock, flags); - if (utmip_pad_count++ == 0) { - val = readl(base + UTMIP_BIAS_CFG0); - val &= ~(UTMIP_OTGPD | UTMIP_BIASPD); - writel(val, base + UTMIP_BIAS_CFG0); - } + utmip_pad_count++; + val = readl(base + UTMIP_BIAS_CFG0); + val &= ~(UTMIP_OTGPD | UTMIP_BIASPD); + writel(val, base + UTMIP_BIAS_CFG0); spin_unlock_irqrestore(&utmip_pad_lock, flags); - clk_disable(phy->pad_clk); } @@ -882,9 +879,6 @@ static int null_phy_power_on(struct tegra_usb_phy *phy, bool is_dpd) void __iomem *base = phy->regs; struct tegra_ulpi_config *config = phy->config; - if (config->preinit) - config->preinit(); - if (!config->trimmer) config->trimmer = &default_trimmer; @@ -896,6 +890,9 @@ static int null_phy_power_on(struct tegra_usb_phy *phy, bool is_dpd) val |= ULPI_OUTPUT_PINMUX_BYP | ULPI_CLKOUT_PINMUX_BYP; writel(val, base + ULPI_TIMING_CTRL_0); + if (config->pre_phy_on && config->pre_phy_on()) + return -EAGAIN; + val = readl(base + USB_SUSP_CTRL); val |= ULPI_PHY_ENABLE; writel(val, base + USB_SUSP_CTRL); @@ -919,6 +916,7 @@ static int null_phy_power_on(struct tegra_usb_phy *phy, bool is_dpd) /* enable null phy mode */ val = ULPIS2S_ENA; val |= ULPIS2S_PLLU_MASTER_BLASTER60; + val |= ULPIS2S_SUPPORT_DISCONNECT; val |= ULPIS2S_SPARE((phy->mode == TEGRA_USB_PHY_MODE_HOST) ? 3 : 1); writel(val, base + ULPIS2S_CTRL); @@ -963,20 +961,27 @@ static int null_phy_power_on(struct tegra_usb_phy *phy, bool is_dpd) USB_PHY_CLK_VALID)) pr_err("%s: timeout waiting for phy to stabilize\n", __func__); - if (config->postinit) - config->postinit(); + if (config->post_phy_on && config->post_phy_on()) + return -EAGAIN; return 0; } -static void null_phy_power_off(struct tegra_usb_phy *phy, bool is_dpd) +static int null_phy_power_off(struct tegra_usb_phy *phy, bool is_dpd) { unsigned long val; void __iomem *base = phy->regs; + struct tegra_ulpi_config *config = phy->config; + + if (config->pre_phy_off && config->pre_phy_off()) + return -EAGAIN; val = readl(base + ULPI_TIMING_CTRL_0); val &= ~ULPI_CLK_PADOUT_ENA; writel(val, base + ULPI_TIMING_CTRL_0); + + if (config->post_phy_off && config->post_phy_off()) + return -EAGAIN; } static int uhsic_phy_power_on(struct tegra_usb_phy *phy, bool is_dpd) |