summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/arm/mach-tegra/board-whistler-baseband.c264
-rw-r--r--arch/arm/mach-tegra/board-whistler-baseband.h31
-rw-r--r--arch/arm/mach-tegra/include/mach/usb_phy.h4
-rw-r--r--arch/arm/mach-tegra/usb_phy.c31
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)