Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5554caa9 authored by Avaneesh Kumar Dwivedi's avatar Avaneesh Kumar Dwivedi Committed by Gerrit - the friendly Code Review server
Browse files

soc: qcom: Add Blackghost(BG) chip PIL driver



BG is a companion chip for msm8909 for low power needs.
Add BG PIL driver to provide load, subsystem restart
and ramdump collection support.

The driver loads the BG firmware into memory and sends
a command to the secure userspace application to initiate
secure transfer of the BG firmware into internal memory
of BG and reset BG. The subsystem restart depends on four
GPIOs present between MSM8909 and BG.

Change-Id: I6e5db44879acb74d39961103715db7417e1bef39
Signed-off-by: default avatarAvaneesh Kumar Dwivedi <akdwived@codeaurora.org>
parent c97f5cfd
Loading
Loading
Loading
Loading
+33 −0
Original line number Diff line number Diff line
Qualcomm Technologies Inc Blackghost(BG) PIL driver:

Blackghost(BG) PIL driver provide interface to load and boot Blackghost(BG)
SOC. Blackghost(BG) SOC is an external SOC which communicate to MSM via
SPI. The PIL driver loads firmware into memory and invoke secure app via
qseecom to transfer and handshake the boot process. Once Blackghost(BG) SOC
is booted it raises ready interrupt which is handled by BG PIL driver, and
this event is informed to other MSM drivers, other remote subsystem via
notifier framework. The PIL driver also handles interrupt for the BG SOC
crash upon arrival of which it reset and initiates ramdump collection for BG
SOC.

Required properties:
- compatible:			Must be "qcom,PIL-blackghost"
- qcom,firmware-name:		Base name of the firmware image.
- qcom,bg2ap-status-gpio:	GPIO used by the blackghost to indicate status to the apps.
- qcom,bg2ap-errfatal-gpio:	GPIO used by the blackghost to indicate software error to the apps.
- qcom,ap2bg-status-gpio:	GPIO used by the apps to indicate its status to blackghost.
- qcom,ap2bg-errfatal-gpio:	GPIO used by the apps to indicate blackghost about apps reset.

Example:

	qcom,blackghost {
		compatible = "qcom,pil-blackghost";
		qcom,firmware-name = "bgelf";

		/* GPIO inputs from blackghost */
		qcom,bg2ap-status-gpio = <&msm_gpio 97 0>;
		qcom,bg2ap-errfatal-gpio = <&msm_gpio 95 0>;
		/* GPIO output to blackghost */
		qcom,ap2bg-status-gpio = <&msm_gpio 17 0>;
		qcom,ap2bg-errfatal-gpio = <&msm_gpio 23 0>;
	};
+9 −0
Original line number Diff line number Diff line
@@ -717,6 +717,15 @@ config MSM_PIL_SSR_GENERIC
	  or a fatal error. Subsystems include LPASS, Venus, VPU, WCNSS and
	  BCSS.

config MSM_PIL_SSR_BG
        tristate "MSM Subsystem Blackghost(BG) Support"
        depends on MSM_PIL && MSM_SUBSYSTEM_RESTART
        help
	  Support for booting and shutting down Blackghost(BG) SOC which is
	  an external SOC. This driver communicates with Blackghost(BG) SOC
	  via pair of IPC GPIOs for inward and outward signals between MSM
	  and Blackghost(BG) SOC.

config MSM_PIL_MSS_QDSP6V5
	tristate "MSS QDSP6v5 (Hexagon) Boot Support"
	depends on MSM_PIL && MSM_SUBSYSTEM_RESTART
+1 −0
Original line number Diff line number Diff line
@@ -76,6 +76,7 @@ obj-$(CONFIG_MSM_GLINK_PKT) += msm_glink_pkt.o
obj-$(CONFIG_MSM_TZ_SMMU) += msm_tz_smmu.o
obj-$(CONFIG_MSM_PIL)   +=      peripheral-loader.o
obj-$(CONFIG_MSM_PIL_SSR_GENERIC) += subsys-pil-tz.o
obj-$(CONFIG_MSM_PIL_SSR_BG) += subsys-pil-bg.o
obj-$(CONFIG_MSM_PIL_MSS_QDSP6V5) += pil-q6v5.o pil-msa.o pil-q6v5-mss.o
obj-$(CONFIG_MSM_SCM_ERRATA) += scm-errata.o
obj-$(CONFIG_MSM_PFE_WA) += pfe-wa.o
+42 −0
Original line number Diff line number Diff line
/* Copyright (c) 2017 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
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#ifndef __BG_INTF_H_
#define __BG_INTF_H_

#define MAX_APP_NAME_SIZE 100
#define RESULT_SUCCESS 0
#define RESULT_FAILURE -1

/* tzapp command list.*/
enum bg_tz_commands {
	BGPIL_RAMDUMP,
	BGPIL_IMAGE_LOAD,
	BGPIL_AUTH_MDT,
};

/* tzapp bg request.*/
__packed struct tzapp_bg_req {
	uint8_t tzapp_bg_cmd;
	phys_addr_t address_fw;
	size_t size_fw;
};

/* tzapp bg response.*/
__packed struct tzapp_bg_rsp {
	uint32_t tzapp_bg_cmd;
	uint32_t bg_info_len;
	uint32_t status;
	uint32_t bg_info[100];
};

#endif
+715 −0
Original line number Diff line number Diff line
/* Copyright (c) 2017, 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
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/interrupt.h>
#include <linux/of_gpio.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/dma-mapping.h>
#include <linux/reboot.h>
#include <soc/qcom/subsystem_restart.h>
#include <soc/qcom/ramdump.h>
#include <soc/qcom/subsystem_notif.h>

#include "peripheral-loader.h"
#include "../../misc/qseecom_kernel.h"
#include "pil_bg_intf.h"

#define INVALID_GPIO	-1
#define NUM_GPIOS	4
#define SECURE_APP	"bgapp"
#define desc_to_data(d)	container_of(d, struct pil_bg_data, desc)
#define subsys_to_data(d) container_of(d, struct pil_bg_data, subsys_desc)

/**
 * struct pil_bg_data
 * @qseecom_handle: handle of TZ app
 * @bg_queue: private queue to schedule worker threads for bottom half
 * @restart_work: work struct for executing ssr
 * @reboot_blk: notification block for reboot event
 * @subsys_desc: subsystem descriptor
 * @subsys: subsystem device pointer
 * @gpios: array to hold all gpio handle
 * @desc: PIL descriptor
 * @address_fw: address where firmware binaries loaded
 * @ramdump_dev: ramdump device pointer
 * @size_fw: size of bg firmware binaries
 * @errfatal_irq: irq number to indicate bg crash or shutdown
 * @status_irq: irq to indicate bg status
 * @app_status: status of tz app loading
 * @is_ready: Is BG chip up
 * @err_ready: The error ready signal
 */

struct pil_bg_data {
	struct qseecom_handle *qseecom_handle;
	struct workqueue_struct *bg_queue;
	struct work_struct restart_work;
	struct notifier_block reboot_blk;
	struct subsys_desc subsys_desc;
	struct subsys_device *subsys;
	unsigned gpios[NUM_GPIOS];
	int errfatal_irq;
	int status_irq;
	struct pil_desc desc;
	phys_addr_t address_fw;
	void *ramdump_dev;
	u32 cmd_status;
	size_t size_fw;
	int app_status;
	bool is_ready;
	struct completion err_ready;
};

/**
 * bg_app_shutdown_notify() - Toggle AP2BG err fatal gpio when
 * called by SSR framework.
 * @subsys: struct containing private BG data.
 *
 * Return: none.
 */
static void bg_app_shutdown_notify(const struct subsys_desc *subsys)
{
	struct pil_bg_data *bg_data = subsys_to_data(subsys);
	/* Toggle AP2BG err fatal gpio here to inform apps err fatal event */
	if (gpio_is_valid(bg_data->gpios[2]))
		gpio_set_value(bg_data->gpios[2], 1);
}

/**
 * bg_app_reboot_notify() - Toggle AP2BG err fatal gpio.
 * @nb: struct containing private BG data.
 *
 * Return: NOTIFY_DONE indicating success.
 */
static int bg_app_reboot_notify(struct notifier_block *nb,
		unsigned long code, void *unused)
{
	struct pil_bg_data *bg_data = container_of(nb,
					struct pil_bg_data, reboot_blk);
	/* Toggle AP2BG err fatal gpio here to inform apps err fatal event */
	if (gpio_is_valid(bg_data->gpios[2]))
		gpio_set_value(bg_data->gpios[2], 1);
	return NOTIFY_DONE;
}

/**
 * get_cmd_rsp_buffers() - Function sets cmd & rsp buffer pointers and
 *                         aligns buffer lengths
 * @hdl:	index of qseecom_handle
 * @cmd:	req buffer - set to qseecom_handle.sbuf
 * @cmd_len:	ptr to req buffer len
 * @rsp:	rsp buffer - set to qseecom_handle.sbuf + offset
 * @rsp_len:	ptr to rsp buffer len
 *
 * Return: Success always .
 */
static int get_cmd_rsp_buffers(struct qseecom_handle *handle, void **cmd,
			uint32_t *cmd_len, void **rsp, uint32_t *rsp_len)
{
	*cmd = handle->sbuf;
	if (*cmd_len & QSEECOM_ALIGN_MASK)
		*cmd_len = QSEECOM_ALIGN(*cmd_len);

	*rsp = handle->sbuf + *cmd_len;
	if (*rsp_len & QSEECOM_ALIGN_MASK)
		*rsp_len = QSEECOM_ALIGN(*rsp_len);

	return 0;
}

/**
 * pil_load_bg_tzapp() - Called to load TZ app.
 * @pbd: struct containing private BG data.
 *
 * Return: 0 on success. Error code on failure.
 */
static int pil_load_bg_tzapp(struct pil_bg_data *pbd)
{
	int rc;

	/* return success if already loaded */
	if (pbd->qseecom_handle && !pbd->app_status)
		return 0;
	/* Load the APP */
	rc = qseecom_start_app(&pbd->qseecom_handle, SECURE_APP, SZ_4K);
	if (rc < 0) {
		dev_err(pbd->desc.dev, "BG TZ app load failure\n");
		pbd->app_status = RESULT_FAILURE;
		return -EIO;
	}
	pbd->app_status = RESULT_SUCCESS;
	return 0;
}

/**
 * bgpil_tzapp_comm() - Function called to communicate with TZ APP.
 * @req:	struct containing command and parameters.
 *
 * Return: 0 on success. Error code on failure.
 */
static long bgpil_tzapp_comm(struct pil_bg_data *pbd,
				struct tzapp_bg_req *req)
{
	struct tzapp_bg_req *bg_tz_req;
	struct tzapp_bg_rsp *bg_tz_rsp;
	int rc, req_len, rsp_len;

	/* Fill command structure */
	req_len = sizeof(struct tzapp_bg_req);
	rsp_len = sizeof(struct tzapp_bg_rsp);
	rc = get_cmd_rsp_buffers(pbd->qseecom_handle,
		(void **)&bg_tz_req, &req_len,
		(void **)&bg_tz_rsp, &rsp_len);
	if (rc)
		goto end;

	bg_tz_req->tzapp_bg_cmd = req->tzapp_bg_cmd;
	bg_tz_req->address_fw = req->address_fw;
	bg_tz_req->size_fw = req->size_fw;
	rc = qseecom_send_command(pbd->qseecom_handle,
		(void *)bg_tz_req, req_len, (void *)bg_tz_rsp, rsp_len);
	pr_debug("BG PIL qseecom returned with value 0x%x and status 0x%x\n",
		rc, bg_tz_rsp->status);
	if (rc || bg_tz_rsp->status)
		pbd->cmd_status = bg_tz_rsp->status;
end:
	return rc;
}

/**
 * wait_for_err_ready() - Called in power_up to wait for error ready.
 * Signal waiting function.
 * @bg_data: BG PIL private structure.
 *
 * Return: 0 on success. Error code on failure.
 */
static int wait_for_err_ready(struct pil_bg_data *bg_data)
{
	int ret;

	if ((!bg_data->status_irq))
		return 0;

	ret = wait_for_completion_timeout(&bg_data->err_ready,
			msecs_to_jiffies(10000));
	if (!ret) {
		pr_err("[%s]: Error ready timed out\n", bg_data->desc.name);
		return -ETIMEDOUT;
	}
	return 0;
}

/**
 * bg_powerup() - Called by SSR framework on userspace invocation.
 * does load tz app and call peripheral loader.
 * @subsys: struct containing private BG data.
 *
 * Return: 0 on success. Error code on failure.
 */
static int bg_powerup(const struct subsys_desc *subsys)
{
	struct pil_bg_data *bg_data = subsys_to_data(subsys);
	int ret;

	init_completion(&bg_data->err_ready);
	if (!bg_data->qseecom_handle) {
		ret = pil_load_bg_tzapp(bg_data);
		if (ret) {
			dev_err(bg_data->desc.dev,
				"%s: BG TZ app load failure\n",
				__func__);
			return ret;
		}
	}
	pr_debug("bgapp loaded\n");
	bg_data->desc.fw_name = subsys->fw_name;

	/* Enable status and err fatal irqs */
	ret = pil_boot(&bg_data->desc);
	if (ret) {
		dev_err(bg_data->desc.dev,
			"%s: BG PIL Boot failed\n", __func__);
		return ret;
	}
	enable_irq(bg_data->status_irq);
	enable_irq(bg_data->errfatal_irq);
	ret = wait_for_err_ready(bg_data);
	if (ret) {
		dev_err(bg_data->desc.dev,
			"[%s:%d]: Timed out waiting for error ready: %s!\n",
			current->comm, current->pid, bg_data->desc.name);
		return ret;
	}
	return ret;
}

/**
 * bg_shutdown() - Called by SSR framework on userspace invocation.
 * disable status interrupt to avoid spurious signal during PRM exit.
 * @subsys: struct containing private BG data.
 * @force_stop: unused
 *
 * Return: always success
 */
static int bg_shutdown(const struct subsys_desc *subsys, bool force_stop)
{
	/*
	 * Just return success for time being as this function
	 * has nothing to do for now. this will act as as placeholder
	 * for any cleanup work that is required later
	 */
	return 0;
}

/**
 * bg_auth_metadata() - Called by Peripheral loader framework
 * send command to tz app for authentication of metadata.
 * @pil: pil descriptor.
 * @metadata: metadata load address
 * @size: size of metadata
 *
 * Return: 0 on success. Error code on failure.
 */
static int bg_auth_metadata(struct pil_desc *pil,
	const u8 *metadata, size_t size)
{
	struct pil_bg_data *bg_data = desc_to_data(pil);
	struct tzapp_bg_req bg_tz_req;
	int ret;

	bg_tz_req.tzapp_bg_cmd = BGPIL_AUTH_MDT;
	bg_tz_req.address_fw = (phys_addr_t)metadata;
	bg_tz_req.size_fw = size;

	ret = bgpil_tzapp_comm(bg_data, &bg_tz_req);
	if (ret || bg_data->cmd_status) {
		dev_err(pil->dev,
			"%s: BGPIL_AUTH_MDT qseecom call failed\n",
				__func__);
		return bg_data->cmd_status;
	}
	pr_debug("BG MDT Authenticated\n");
	return 0;
}

/**
 * bg_get_firmware_addr() - Called by Peripheral loader framework
 * to get address and size of bg firmware binaries.
 * @pil: pil descriptor.
 * @addr: fw load address
 * @size: size of fw
 *
 * Return: 0 on success.
 */
static int bg_get_firmware_addr(struct pil_desc *pil,
					phys_addr_t addr, size_t size)
{
	struct pil_bg_data *bg_data = desc_to_data(pil);

	bg_data->address_fw = addr;
	bg_data->size_fw = size;
	pr_debug("BG PIL loads firmware blobs at 0x%x with size 0x%x\n",
		addr, size);
	return 0;
}

/**
 * bg_auth_and_xfer() - Called by Peripheral loader framework
 * to signal tz app to authenticate and boot bg chip.
 * @pil: pil descriptor.
 *
 * Return: 0 on success. Error code on failure.
 */
static int bg_auth_and_xfer(struct pil_desc *pil)
{
	struct pil_bg_data *bg_data = desc_to_data(pil);
	struct tzapp_bg_req bg_tz_req;
	int ret;

	bg_tz_req.tzapp_bg_cmd = BGPIL_IMAGE_LOAD;
	bg_tz_req.address_fw = bg_data->address_fw;
	bg_tz_req.size_fw = bg_data->size_fw;

	ret = bgpil_tzapp_comm(bg_data, &bg_tz_req);
	if (ret || bg_data->cmd_status) {
		dev_err(pil->dev,
			"%s: BGPIL_IMAGE_LOAD qseecom call failed\n",
			__func__);
		pil_free_memory(&bg_data->desc);
		return bg_data->cmd_status;
	}
	/* BG Transfer of image is complete, free up the memory */
	pr_debug("BG Firmware authentication and transfer done\n");
	pil_free_memory(&bg_data->desc);
	return 0;
}

/**
 * bg_ramdump() - Called by SSR framework to save dump of BG internal
 * memory, BG PIL does allocate region from dynamic memory and pass this
 * region to tz to dump memory content of BG.
 * @subsys: subsystem descriptor.
 *
 * Return: 0 on success. Error code on failure.
 */
static int bg_ramdump(int enable, const struct subsys_desc *subsys)
{
	struct pil_bg_data *bg_data = subsys_to_data(subsys);
	struct pil_desc desc = bg_data->desc;
	struct ramdump_segment *ramdump_segments;
	struct tzapp_bg_req bg_tz_req;
	phys_addr_t start_addr;
	size_t size = SZ_1M;
	void *region;
	int ret;

	init_dma_attrs(&desc.attrs);
	dma_set_attr(DMA_ATTR_SKIP_ZEROING, &desc.attrs);
	dma_set_attr(DMA_ATTR_NO_KERNEL_MAPPING, &desc.attrs);

	region = dma_alloc_attrs(desc.dev, SZ_1M,
				&start_addr, GFP_KERNEL, &desc.attrs);

	if (region == NULL) {
		dev_dbg(desc.dev,
			"BG PIL failure to allocate ramdump region of size %zx\n",
			size);
		return -ENOMEM;
	}

	bg_tz_req.tzapp_bg_cmd = BGPIL_RAMDUMP;
	bg_tz_req.address_fw = start_addr;
	bg_tz_req.size_fw = SZ_1M;

	ret = bgpil_tzapp_comm(bg_data, &bg_tz_req);
	if (ret || bg_data->cmd_status) {
		dev_dbg(desc.dev, "%s: BG PIL ramdump collection failed\n",
			__func__);
		return bg_data->cmd_status;
	}

	ramdump_segments->address = start_addr;
	ramdump_segments->size = SZ_1M;

	ret = do_elf_ramdump(bg_data->ramdump_dev, ramdump_segments, 1);
	kfree(ramdump_segments);
	return 0;
}

static struct pil_reset_ops pil_ops_trusted = {
	.init_image = bg_auth_metadata,
	.mem_setup =  bg_get_firmware_addr,
	.auth_and_reset = bg_auth_and_xfer,
	.shutdown = NULL,
	.proxy_vote = NULL,
	.proxy_unvote = NULL,
};

/**
 * bg_restart_work() - scheduled by interrupt handler to carry
 * out ssr sequence
 * @work: work struct.
 *
 * Return: none.
 */
static void bg_restart_work(struct work_struct *work)
{
	struct pil_bg_data *drvdata =
		container_of(work, struct pil_bg_data, restart_work);
		subsystem_restart_dev(drvdata->subsys);
}

static irqreturn_t bg_errfatal(int irq, void *dev_id)
{
	struct pil_bg_data *drvdata = (struct pil_bg_data *)dev_id;

	if (!drvdata)
		return IRQ_HANDLED;

	dev_dbg(drvdata->desc.dev, "BG s/w err fatal\n");
	queue_work(drvdata->bg_queue, &drvdata->restart_work);
	drvdata->is_ready = false;
	return IRQ_HANDLED;
}

static irqreturn_t bg_status_change(int irq, void *dev_id)
{
	bool value;
	struct pil_bg_data *drvdata = (struct pil_bg_data *)dev_id;

	if (!drvdata)
		return IRQ_HANDLED;

	value = gpio_get_value(drvdata->gpios[0]);
	if (value == true && !drvdata->is_ready) {
		dev_info(drvdata->desc.dev,
			"BG services are up and running: irq state changed 0->1\n");
		drvdata->is_ready = true;
		complete(&drvdata->err_ready);
	} else if (value == false && drvdata->is_ready) {
		dev_err(drvdata->desc.dev,
			"BG got unexpected reset: irq state changed 1->0\n");
		queue_work(drvdata->bg_queue, &drvdata->restart_work);
	}
	return IRQ_HANDLED;
}

/**
 * setup_bg_gpio_irq() - called in probe to configure input/
 * output gpio.
 * @drvdata: private data struct for BG.
 *
 * Return: 0 on success. Error code on failure.
 */
static int setup_bg_gpio_irq(struct platform_device *pdev,
					struct pil_bg_data *drvdata)
{
	int ret = -1;
	int irq, i;

	if (gpio_request(drvdata->gpios[0], "BG2AP_STATUS")) {
		dev_err(&pdev->dev,
			"%s Failed to configure BG2AP_STATUS gpio\n",
				__func__);
		goto err;
	}
	if (gpio_request(drvdata->gpios[1], "BG2AP_ERRFATAL")) {
		dev_err(&pdev->dev,
			"%s Failed to configure BG2AP_ERRFATAL gpio\n",
				__func__);
		goto err;
	}
	gpio_direction_input(drvdata->gpios[0]);
	gpio_direction_input(drvdata->gpios[1]);
	/* BG2AP STATUS IRQ */
	irq = gpio_to_irq(drvdata->gpios[0]);
	if (irq < 0) {
		dev_err(&pdev->dev,
		"%s: bad BG2AP_STATUS IRQ resource, err = %d\n",
			__func__, irq);
		goto err;
	}
	ret = request_irq(irq, bg_status_change,
		IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
		"bg2ap_status", drvdata);
	if (ret < 0) {
		dev_err(&pdev->dev,
			"%s: BG2AP_STATUS IRQ#%d request failed, err=%d",
				__func__, irq, ret);
		goto err;
	}
	drvdata->status_irq = irq;
	/* BG2AP ERR_FATAL irq. */
	irq = gpio_to_irq(drvdata->gpios[1]);
	if (irq < 0) {
		dev_err(&pdev->dev, "bad BG2AP_ERRFATAL IRQ resource\n");
		goto err;
	}
	ret = request_irq(irq, bg_errfatal,
		IRQF_TRIGGER_RISING | IRQF_ONESHOT, "bg2ap_errfatal", drvdata);
	if (ret < 0) {
		dev_err(&pdev->dev,
			"%s: BG2AP_ERRFATAL IRQ#%d request failed,\n",
				__func__, irq);
		goto err;
	}
	drvdata->errfatal_irq = irq;
	/* Configure outgoing GPIO's */
	if (gpio_request(drvdata->gpios[2], "AP2BG_ERRFATAL")) {
		dev_err(&pdev->dev,
			"%s Failed to configure AP2BG_ERRFATAL gpio\n",
				__func__);
		goto err;
	}
	if (gpio_request(drvdata->gpios[3], "AP2BG_STATUS")) {
		dev_err(&pdev->dev,
			"%s Failed to configure AP2BG_STATUS gpio\n",
				__func__);
		goto err;
	}
	/*
	 * Put status gpio in default high state which will
	 * make transition to low on any sudden reset case of msm
	 */
	gpio_direction_output(drvdata->gpios[2], 0);
	gpio_direction_output(drvdata->gpios[3], 1);
	/* Inform BG that AP is up */
	gpio_set_value(drvdata->gpios[3], 1);
	return 0;
err:
	for (i = 0; i < NUM_GPIOS; ++i) {
		if (gpio_is_valid(drvdata->gpios[i]))
			gpio_free(drvdata->gpios[i]);
	}
	return ret;
}

/**
 * bg_dt_parse_gpio() - called in probe to parse gpio's
 * @drvdata: private data struct for BG.
 *
 * Return: 0 on success. Error code on failure.
 */
static int bg_dt_parse_gpio(struct platform_device *pdev,
				struct pil_bg_data *drvdata)
{
	int i, val;

	for (i = 0; i < NUM_GPIOS; i++)
		drvdata->gpios[i] = INVALID_GPIO;
	val = of_get_named_gpio(pdev->dev.of_node,
					"qcom,bg2ap-status-gpio", 0);
	if (val >= 0)
		drvdata->gpios[0] = val;
	else {
		pr_err("BG status gpio not found, error=%d\n", val);
		return -EINVAL;
	}
	val = of_get_named_gpio(pdev->dev.of_node,
					"qcom,bg2ap-errfatal-gpio", 0);
	if (val >= 0)
		drvdata->gpios[1] = val;
	else {
		pr_err("BG err-fatal gpio not found, error=%d\n", val);
		return -EINVAL;
	}
	val = of_get_named_gpio(pdev->dev.of_node,
					"qcom,ap2bg-errfatal-gpio", 0);
	if (val >= 0)
		drvdata->gpios[2] = val;
	else {
		pr_err("ap2bg err-fatal gpio not found, error=%d\n", val);
		return -EINVAL;
	}
	val = of_get_named_gpio(pdev->dev.of_node,
					"qcom,ap2bg-status-gpio", 0);
	if (val >= 0)
		drvdata->gpios[3] = val;
	else {
		pr_err("ap2bg status gpio not found, error=%d\n", val);
		return -EINVAL;
	}
	return 0;
}

static int pil_bg_driver_probe(struct platform_device *pdev)
{
	struct pil_bg_data *bg_data;
	int rc;

	bg_data = devm_kzalloc(&pdev->dev, sizeof(*bg_data), GFP_KERNEL);
	if (!bg_data)
		return -ENOMEM;
	platform_set_drvdata(pdev, bg_data);
	rc = of_property_read_string(pdev->dev.of_node,
			"qcom,firmware-name", &bg_data->desc.name);
	if (rc)
		return rc;
	bg_data->desc.dev = &pdev->dev;
	bg_data->desc.owner = THIS_MODULE;
	bg_data->desc.ops = &pil_ops_trusted;
	rc = pil_desc_init(&bg_data->desc);
	if (rc)
		return rc;
	/* Read gpio configuration */
	rc = bg_dt_parse_gpio(pdev, bg_data);
	if (rc)
		return rc;
	rc = setup_bg_gpio_irq(pdev, bg_data);
	if (rc < 0)
		return rc;
	bg_data->subsys_desc.name = bg_data->desc.name;
	bg_data->subsys_desc.owner = THIS_MODULE;
	bg_data->subsys_desc.dev = &pdev->dev;
	bg_data->subsys_desc.shutdown = bg_shutdown;
	bg_data->subsys_desc.powerup = bg_powerup;
	bg_data->subsys_desc.ramdump = bg_ramdump;
	bg_data->subsys_desc.free_memory = NULL;
	bg_data->subsys_desc.crash_shutdown = bg_app_shutdown_notify;
	bg_data->ramdump_dev =
		create_ramdump_device(bg_data->subsys_desc.name, &pdev->dev);
	if (!bg_data->ramdump_dev) {
		rc = -ENOMEM;
		goto err_ramdump;
	}
	bg_data->subsys = subsys_register(&bg_data->subsys_desc);
	if (IS_ERR(bg_data->subsys)) {
		rc = PTR_ERR(bg_data->subsys);
		goto err_subsys;
	}

	bg_data->desc.subsys_dev = bg_data->subsys;
	bg_data->reboot_blk.notifier_call = bg_app_reboot_notify;
	register_reboot_notifier(&bg_data->reboot_blk);

	bg_data->bg_queue = alloc_workqueue("bg_queue", 0, 0);
	if (!bg_data->bg_queue) {
		dev_err(&pdev->dev, "could not create bg_queue\n");
		subsys_unregister(bg_data->subsys);
		goto err_subsys;
	}
	INIT_WORK(&bg_data->restart_work, bg_restart_work);
	return 0;
err_subsys:
	destroy_ramdump_device(bg_data->ramdump_dev);
err_ramdump:
	pil_desc_release(&bg_data->desc);
	return rc;
}

static int pil_bg_driver_exit(struct platform_device *pdev)
{
	struct pil_bg_data *bg_data = platform_get_drvdata(pdev);

	subsys_unregister(bg_data->subsys);
	destroy_ramdump_device(bg_data->ramdump_dev);
	pil_desc_release(&bg_data->desc);

	return 0;
}

static struct of_device_id pil_bg_match_table[] = {
	{.compatible = "qcom,pil-blackghost"},
	{}
};

static struct platform_driver pil_bg_driver = {
	.probe = pil_bg_driver_probe,
	.remove = pil_bg_driver_exit,
	.driver = {
		.name = "subsys-pil-bg",
		.of_match_table = pil_bg_match_table,
		.owner = THIS_MODULE,
	},
};

static int __init pil_bg_init(void)
{
	return platform_driver_register(&pil_bg_driver);
}
module_init(pil_bg_init);

static void __exit pil_bg_exit(void)
{
	platform_driver_unregister(&pil_bg_driver);
}
module_exit(pil_bg_exit);

MODULE_DESCRIPTION("Support for booting QTI Blackghost SoC");
MODULE_LICENSE("GPL v2");