esoc: Add support for sdxpoorwills

Add support for sdxpoorwills, using mdm9x55 data as a starting point.

Change-Id: Ic1abf2997781e84ab20270d974794ed37c20d62a
Signed-off-by: Raghavendra Rao Ananta <rananta@codeaurora.org>
diff --git a/drivers/esoc/Kconfig b/drivers/esoc/Kconfig
index 3c65f69..4613150 100644
--- a/drivers/esoc/Kconfig
+++ b/drivers/esoc/Kconfig
@@ -38,7 +38,7 @@
 	  allow logging of different esoc driver traces.
 
 config ESOC_MDM_4x
-	bool "Add support for external mdm9x25/mdm9x35/mdm9x55"
+	bool "Add support for external modem"
 	help
 	  In some Qualcomm Technologies, Inc. boards, an external modem such as
 	  mdm9x25 or mdm9x35 is connected to a primary msm. The primary soc can
@@ -49,7 +49,7 @@
 	tristate "Command engine for 4x series external modems"
 	help
 	  Provides a command engine to control the behavior of an external modem
-	  such as mdm9x25/mdm9x35/mdm9x55/QSC. Allows the primary soc to put the
+	  such as mdm9x25/mdm9x35/mdm9x55/sdxpoorwills/QSC. Allows the primary soc to put the
 	  external modem in a specific mode. Also listens for events on the
 	  external modem.
 
diff --git a/drivers/esoc/esoc-mdm-4x.c b/drivers/esoc/esoc-mdm-4x.c
index 677e21d..bbec9d3 100644
--- a/drivers/esoc/esoc-mdm-4x.c
+++ b/drivers/esoc/esoc-mdm-4x.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2014-2015, 2017, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2014-2015, 2017-2018, The Linux Foundation. All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 and
@@ -794,6 +794,28 @@
 	mdm->gpio_state_running = NULL;
 	return retval;
 }
+
+static void mdm_release_ipc_gpio(struct mdm_ctrl *mdm)
+{
+	int i;
+
+	if (!mdm)
+		return;
+
+	for (i = 0; i < NUM_GPIOS; ++i)
+		if (gpio_is_valid(MDM_GPIO(mdm, i)))
+			gpio_free(MDM_GPIO(mdm, i));
+}
+
+static void mdm_free_irq(struct mdm_ctrl *mdm)
+{
+	if (!mdm)
+		return;
+
+	free_irq(mdm->errfatal_irq, mdm);
+	free_irq(mdm->status_irq, mdm);
+}
+
 static int mdm9x25_setup_hw(struct mdm_ctrl *mdm,
 					const struct mdm_ops *ops,
 					struct platform_device *pdev)
@@ -1028,6 +1050,108 @@
 	return 0;
 }
 
+static int sdxpoorwills_setup_hw(struct mdm_ctrl *mdm,
+				const struct mdm_ops *ops,
+				struct platform_device *pdev)
+{
+	int ret;
+	struct device_node *node;
+	struct esoc_clink *esoc;
+	const struct esoc_clink_ops *clink_ops = ops->clink_ops;
+	const struct mdm_pon_ops *pon_ops = ops->pon_ops;
+
+	mdm->dev = &pdev->dev;
+	mdm->pon_ops = pon_ops;
+	node = pdev->dev.of_node;
+
+	esoc = devm_kzalloc(mdm->dev, sizeof(*esoc), GFP_KERNEL);
+	if (IS_ERR_OR_NULL(esoc)) {
+		dev_err(mdm->dev, "cannot allocate esoc device\n");
+		return PTR_ERR(esoc);
+	}
+
+	esoc->pdev = pdev;
+
+	mdm->mdm_queue = alloc_workqueue("mdm_queue", 0, 0);
+	if (!mdm->mdm_queue) {
+		dev_err(mdm->dev, "could not create mdm_queue\n");
+		return -ENOMEM;
+	}
+
+	mdm->irq_mask = 0;
+	mdm->ready = false;
+
+	ret = mdm_dt_parse_gpios(mdm);
+	if (ret) {
+		dev_err(mdm->dev, "Failed to parse DT gpios\n");
+		goto err_destroy_wrkq;
+	}
+
+	ret = mdm_pon_dt_init(mdm);
+	if (ret) {
+		dev_err(mdm->dev, "Failed to parse PON DT gpio\n");
+		goto err_destroy_wrkq;
+	}
+
+	ret = mdm_pinctrl_init(mdm);
+	if (ret) {
+		dev_err(mdm->dev, "Failed to init pinctrl\n");
+		goto err_destroy_wrkq;
+	}
+
+	ret = mdm_pon_setup(mdm);
+	if (ret) {
+		dev_err(mdm->dev, "Failed to setup PON\n");
+		goto err_destroy_wrkq;
+	}
+
+	ret = mdm_configure_ipc(mdm, pdev);
+	if (ret) {
+		dev_err(mdm->dev, "Failed to configure the ipc\n");
+		goto err_release_ipc;
+	}
+
+	esoc->name = SDXPOORWILLS_LABEL;
+	esoc->link_name = SDXPOORWILLS_PCIE;
+
+	ret = of_property_read_string(node, "qcom,mdm-link-info",
+					&esoc->link_info);
+	if (ret)
+		dev_info(mdm->dev, "esoc link info missing\n");
+
+	esoc->clink_ops = clink_ops;
+	esoc->parent = mdm->dev;
+	esoc->owner = THIS_MODULE;
+	esoc->np = pdev->dev.of_node;
+	set_esoc_clink_data(esoc, mdm);
+
+	ret = esoc_clink_register(esoc);
+	if (ret) {
+		dev_err(mdm->dev, "esoc registration failed\n");
+		goto err_free_irq;
+	}
+	dev_dbg(mdm->dev, "esoc registration done\n");
+
+	init_completion(&mdm->debug_done);
+	INIT_WORK(&mdm->mdm_status_work, mdm_status_fn);
+	INIT_WORK(&mdm->restart_reason_work, mdm_get_restart_reason);
+	INIT_DELAYED_WORK(&mdm->mdm2ap_status_check_work, mdm2ap_status_check);
+	mdm->get_restart_reason = false;
+	mdm->debug_fail = false;
+	mdm->esoc = esoc;
+	mdm->init = 0;
+
+	return 0;
+
+err_free_irq:
+	mdm_free_irq(mdm);
+err_release_ipc:
+	mdm_release_ipc_gpio(mdm);
+err_destroy_wrkq:
+	destroy_workqueue(mdm->mdm_queue);
+	return ret;
+}
+
 static struct esoc_clink_ops mdm_cops = {
 	.cmd_exe = mdm_cmd_exe,
 	.get_status = mdm_get_status,
@@ -1053,6 +1177,12 @@
 	.pon_ops = &mdm9x55_pon_ops,
 };
 
+static struct mdm_ops sdxpoorwills_ops = {
+	.clink_ops = &mdm_cops,
+	.config_hw = sdxpoorwills_setup_hw,
+	.pon_ops = &sdxpoorwills_pon_ops,
+};
+
 static const struct of_device_id mdm_dt_match[] = {
 	{ .compatible = "qcom,ext-mdm9x25",
 		.data = &mdm9x25_ops, },
@@ -1060,6 +1190,8 @@
 		.data = &mdm9x35_ops, },
 	{ .compatible = "qcom,ext-mdm9x55",
 		.data = &mdm9x55_ops, },
+	{ .compatible = "qcom,ext-sdxpoorwills",
+		.data = &sdxpoorwills_ops, },
 	{},
 };
 MODULE_DEVICE_TABLE(of, mdm_dt_match);
diff --git a/drivers/esoc/esoc-mdm-drv.c b/drivers/esoc/esoc-mdm-drv.c
index 77ae84b..4291bbc 100644
--- a/drivers/esoc/esoc-mdm-drv.c
+++ b/drivers/esoc/esoc-mdm-drv.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2013-2018, The Linux Foundation. All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 and
@@ -309,6 +309,10 @@
 		.name = "MDM9x55",
 		.data = NULL,
 	},
+	{
+		.name = "SDXPOORWILLS",
+		.data = NULL,
+	},
 };
 
 static struct esoc_drv esoc_ssr_drv = {
diff --git a/drivers/esoc/esoc-mdm-pon.c b/drivers/esoc/esoc-mdm-pon.c
index 0e85776..9624275 100644
--- a/drivers/esoc/esoc-mdm-pon.c
+++ b/drivers/esoc/esoc-mdm-pon.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2014-2015, 2017, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2014-2015, 2017-2018, The Linux Foundation. All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 and
@@ -60,6 +60,24 @@
 	return 0;
 }
 
+/* This function can be called from atomic context. */
+static int sdxpoorwills_toggle_soft_reset(struct mdm_ctrl *mdm, bool atomic)
+{
+	int soft_reset_direction_assert = mdm->soft_reset_inverted;
+
+	gpio_direction_output(MDM_GPIO(mdm, AP2MDM_SOFT_RESET),
+			soft_reset_direction_assert);
+	/*
+	 * Allow PS hold assert to be detected
+	 */
+	if (!atomic)
+		usleep_range(80000, 180000);
+	else
+		mdelay(100);
+	gpio_direction_output(MDM_GPIO(mdm, AP2MDM_SOFT_RESET),
+			!soft_reset_direction_assert);
+	return 0;
+}
 
 static int mdm4x_do_first_power_on(struct mdm_ctrl *mdm)
 {
@@ -99,6 +117,7 @@
 {
 	struct device *dev = mdm->dev;
 	int soft_reset_direction = mdm->soft_reset_inverted ? 1 : 0;
+
 	/* Assert the soft reset line whether mdm2ap_status went low or not */
 	gpio_direction_output(MDM_GPIO(mdm, AP2MDM_SOFT_RESET),
 					soft_reset_direction);
@@ -135,6 +154,27 @@
 	return 0;
 }
 
+static int sdxpoorwills_power_down(struct mdm_ctrl *mdm)
+{
+	struct device *dev = mdm->dev;
+	int soft_reset_direction = mdm->soft_reset_inverted ? 1 : 0;
+
+	/* Assert the soft reset line whether mdm2ap_status went low or not */
+	gpio_direction_output(MDM_GPIO(mdm, AP2MDM_SOFT_RESET),
+					soft_reset_direction);
+	dev_info(dev, "Doing a hard reset\n");
+	gpio_direction_output(MDM_GPIO(mdm, AP2MDM_SOFT_RESET),
+						soft_reset_direction);
+	/*
+	 * Currently, there is a debounce timer on the charm PMIC. It is
+	 * necessary to hold the PMIC RESET low for 325ms
+	 * for the reset to fully take place. Sleep here to ensure the
+	 * reset has occurred before the function exits.
+	 */
+	mdelay(325);
+	return 0;
+}
+
 static void mdm4x_cold_reset(struct mdm_ctrl *mdm)
 {
 	if (!gpio_is_valid(MDM_GPIO(mdm, AP2MDM_SOFT_RESET)))
@@ -158,6 +198,16 @@
 			!mdm->soft_reset_inverted);
 }
 
+static void sdxpoorwills_cold_reset(struct mdm_ctrl *mdm)
+{
+	dev_info(mdm->dev, "Triggering mdm cold reset");
+	gpio_direction_output(MDM_GPIO(mdm, AP2MDM_SOFT_RESET),
+			!!mdm->soft_reset_inverted);
+	mdelay(600);
+	gpio_direction_output(MDM_GPIO(mdm, AP2MDM_SOFT_RESET),
+			!mdm->soft_reset_inverted);
+}
+
 static int mdm4x_pon_dt_init(struct mdm_ctrl *mdm)
 {
 	int val;
@@ -215,3 +265,12 @@
 	.dt_init = mdm4x_pon_dt_init,
 	.setup = mdm4x_pon_setup,
 };
+
+struct mdm_pon_ops sdxpoorwills_pon_ops = {
+	.pon = mdm4x_do_first_power_on,
+	.soft_reset = sdxpoorwills_toggle_soft_reset,
+	.poff_force = sdxpoorwills_power_down,
+	.cold_reset = sdxpoorwills_cold_reset,
+	.dt_init = mdm4x_pon_dt_init,
+	.setup = mdm4x_pon_setup,
+};
diff --git a/drivers/esoc/esoc-mdm.h b/drivers/esoc/esoc-mdm.h
index 621d913..baf4e0b 100644
--- a/drivers/esoc/esoc-mdm.h
+++ b/drivers/esoc/esoc-mdm.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2014-2015, 2017, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2014-2015, 2017-2018, The Linux Foundation. All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 and
@@ -35,6 +35,8 @@
 #define MDM9x35_HSIC			"HSIC"
 #define MDM9x55_LABEL			"MDM9x55"
 #define MDM9x55_PCIE			"PCIe"
+#define SDXPOORWILLS_LABEL		"SDXPOORWILLS"
+#define SDXPOORWILLS_PCIE		"PCIe"
 #define MDM2AP_STATUS_TIMEOUT_MS	120000L
 #define MDM_MODEM_TIMEOUT		3000
 #define DEF_RAMDUMP_TIMEOUT		120000
@@ -150,4 +152,5 @@
 extern struct mdm_pon_ops mdm9x25_pon_ops;
 extern struct mdm_pon_ops mdm9x35_pon_ops;
 extern struct mdm_pon_ops mdm9x55_pon_ops;
+extern struct mdm_pon_ops sdxpoorwills_pon_ops;
 #endif