summaryrefslogtreecommitdiffstats
path: root/drivers/phy
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/phy')
-rw-r--r--drivers/phy/motorola/phy-mapphone-mdm6600.c85
1 files changed, 85 insertions, 0 deletions
diff --git a/drivers/phy/motorola/phy-mapphone-mdm6600.c b/drivers/phy/motorola/phy-mapphone-mdm6600.c
index 5439dd90d0dd..23705e1a0023 100644
--- a/drivers/phy/motorola/phy-mapphone-mdm6600.c
+++ b/drivers/phy/motorola/phy-mapphone-mdm6600.c
@@ -19,6 +19,8 @@
#define PHY_MDM6600_PHY_DELAY_MS 4000 /* PHY enable 2.2s to 3.5s */
#define PHY_MDM6600_ENABLED_DELAY_MS 8000 /* 8s more total for MDM6600 */
+#define MDM6600_MODEM_IDLE_DELAY_MS 1000 /* modem after USB suspend */
+#define MDM6600_MODEM_WAKE_DELAY_MS 200 /* modem response after idle */
enum phy_mdm6600_ctrl_lines {
PHY_MDM6600_ENABLE, /* USB PHY enable */
@@ -93,9 +95,11 @@ struct phy_mdm6600 {
struct gpio_descs *cmd_gpios;
struct delayed_work bootup_work;
struct delayed_work status_work;
+ struct delayed_work modem_wake_work;
struct completion ack;
bool enabled; /* mdm6600 phy enabled */
bool running; /* mdm6600 boot done */
+ bool awake; /* mdm6600 respnds on n_gsm */
int status;
};
@@ -446,6 +450,62 @@ static void phy_mdm6600_deferred_power_on(struct work_struct *work)
dev_err(ddata->dev, "Device not functional\n");
}
+/*
+ * USB suspend puts mdm6600 into low power mode. For any n_gsm using apps,
+ * we need to keep the modem awake by kicking it's mode0 GPIO. This will
+ * keep the modem awake for about 1.2 seconds. When no n_gsm apps are using
+ * the modem, runtime PM auto mode can be enabled so modem can enter low
+ * power mode.
+ */
+static void phy_mdm6600_wake_modem(struct phy_mdm6600 *ddata)
+{
+ struct gpio_desc *mode_gpio0;
+
+ mode_gpio0 = ddata->mode_gpios->desc[PHY_MDM6600_MODE0];
+ gpiod_set_value_cansleep(mode_gpio0, 1);
+ usleep_range(5, 15);
+ gpiod_set_value_cansleep(mode_gpio0, 0);
+ if (ddata->awake)
+ usleep_range(5, 15);
+ else
+ msleep(MDM6600_MODEM_WAKE_DELAY_MS);
+}
+
+static void phy_mdm6600_modem_wake(struct work_struct *work)
+{
+ struct phy_mdm6600 *ddata;
+
+ ddata = container_of(work, struct phy_mdm6600, modem_wake_work.work);
+ phy_mdm6600_wake_modem(ddata);
+ schedule_delayed_work(&ddata->modem_wake_work,
+ msecs_to_jiffies(MDM6600_MODEM_IDLE_DELAY_MS));
+}
+
+static int __maybe_unused phy_mdm6600_runtime_suspend(struct device *dev)
+{
+ struct phy_mdm6600 *ddata = dev_get_drvdata(dev);
+
+ cancel_delayed_work_sync(&ddata->modem_wake_work);
+ ddata->awake = false;
+
+ return 0;
+}
+
+static int __maybe_unused phy_mdm6600_runtime_resume(struct device *dev)
+{
+ struct phy_mdm6600 *ddata = dev_get_drvdata(dev);
+
+ phy_mdm6600_modem_wake(&ddata->modem_wake_work.work);
+ ddata->awake = true;
+
+ return 0;
+}
+
+static const struct dev_pm_ops phy_mdm6600_pm_ops = {
+ SET_RUNTIME_PM_OPS(phy_mdm6600_runtime_suspend,
+ phy_mdm6600_runtime_resume, NULL)
+};
+
static const struct of_device_id phy_mdm6600_id_table[] = {
{ .compatible = "motorola,mapphone-mdm6600", },
{},
@@ -464,6 +524,7 @@ static int phy_mdm6600_probe(struct platform_device *pdev)
INIT_DELAYED_WORK(&ddata->bootup_work,
phy_mdm6600_deferred_power_on);
INIT_DELAYED_WORK(&ddata->status_work, phy_mdm6600_status);
+ INIT_DELAYED_WORK(&ddata->modem_wake_work, phy_mdm6600_modem_wake);
init_completion(&ddata->ack);
ddata->dev = &pdev->dev;
@@ -500,6 +561,24 @@ static int phy_mdm6600_probe(struct platform_device *pdev)
*/
msleep(PHY_MDM6600_PHY_DELAY_MS + 500);
+ /*
+ * Enable PM runtime only after PHY has been powered up properly.
+ * It is currently only needed after USB suspends mdm6600 and n_gsm
+ * needs to access the device. We don't want to do this earlier as
+ * gpio mode0 pin doubles as mdm6600 wake-up gpio.
+ */
+ pm_runtime_use_autosuspend(ddata->dev);
+ pm_runtime_set_autosuspend_delay(ddata->dev,
+ MDM6600_MODEM_IDLE_DELAY_MS);
+ pm_runtime_enable(ddata->dev);
+ error = pm_runtime_get_sync(ddata->dev);
+ if (error < 0) {
+ dev_warn(ddata->dev, "failed to wake modem: %i\n", error);
+ pm_runtime_put_noidle(ddata->dev);
+ }
+ pm_runtime_mark_last_busy(ddata->dev);
+ pm_runtime_put_autosuspend(ddata->dev);
+
return 0;
cleanup:
@@ -512,6 +591,10 @@ static int phy_mdm6600_remove(struct platform_device *pdev)
struct phy_mdm6600 *ddata = platform_get_drvdata(pdev);
struct gpio_desc *reset_gpio = ddata->ctrl_gpios[PHY_MDM6600_RESET];
+ pm_runtime_dont_use_autosuspend(ddata->dev);
+ pm_runtime_put_sync(ddata->dev);
+ pm_runtime_disable(ddata->dev);
+
if (!ddata->running)
wait_for_completion_timeout(&ddata->ack,
msecs_to_jiffies(PHY_MDM6600_ENABLED_DELAY_MS));
@@ -519,6 +602,7 @@ static int phy_mdm6600_remove(struct platform_device *pdev)
gpiod_set_value_cansleep(reset_gpio, 1);
phy_mdm6600_device_power_off(ddata);
+ cancel_delayed_work_sync(&ddata->modem_wake_work);
cancel_delayed_work_sync(&ddata->bootup_work);
cancel_delayed_work_sync(&ddata->status_work);
@@ -530,6 +614,7 @@ static struct platform_driver phy_mdm6600_driver = {
.remove = phy_mdm6600_remove,
.driver = {
.name = "phy-mapphone-mdm6600",
+ .pm = &phy_mdm6600_pm_ops,
.of_match_table = of_match_ptr(phy_mdm6600_id_table),
},
};