slimbus: Add support for report-absent and device-down notification

device_down callback will be called when the device sends
report-absent message per slimbus specification, or if the device
cannot be reached due to some other reason (e.g. bus going down).
device_up will be called when the device sends reports present again.

Change-Id: Ie48c2e3891b55cb1ec9c675a32cdb0a568fdcc18
Signed-off-by: Sagar Dharia <sdharia@codeaurora.org>
diff --git a/drivers/slimbus/slim-msm-ngd.c b/drivers/slimbus/slim-msm-ngd.c
index 205bf37..0c9959c 100644
--- a/drivers/slimbus/slim-msm-ngd.c
+++ b/drivers/slimbus/slim-msm-ngd.c
@@ -998,7 +998,6 @@
 		container_of(qmi, struct msm_slim_ctrl, qmi);
 	struct slim_controller *ctrl = &dev->ctrl;
 	struct slim_device *sbdev;
-	int i;
 
 	ngd_slim_enable(dev, false);
 	/* disconnect BAM pipes */
@@ -1007,14 +1006,9 @@
 	if (dev->use_tx_msgqs == MSM_MSGQ_ENABLED)
 		dev->use_tx_msgqs = MSM_MSGQ_DOWN;
 	msm_slim_sps_exit(dev, false);
-	mutex_lock(&ctrl->m_ctrl);
 	/* device up should be called again after SSR */
 	list_for_each_entry(sbdev, &ctrl->devs, dev_list)
-		sbdev->notified = false;
-	/* invalidate logical addresses */
-	for (i = 0; i < ctrl->num_dev; i++)
-		ctrl->addrt[i].valid = false;
-	mutex_unlock(&ctrl->m_ctrl);
+		slim_report_absent(sbdev);
 	pr_info("SLIM ADSP SSR (DOWN) done");
 }
 
diff --git a/drivers/slimbus/slimbus.c b/drivers/slimbus/slimbus.c
index 201470f..b074289 100644
--- a/drivers/slimbus/slimbus.c
+++ b/drivers/slimbus/slimbus.c
@@ -286,20 +286,41 @@
 	.release	= slim_dev_release,
 };
 
-static void slim_report_present(struct work_struct *work)
+static void slim_report(struct work_struct *work)
 {
 	u8 laddr;
-	int ret;
+	int ret, i;
 	struct slim_driver *sbdrv;
 	struct slim_device *sbdev =
 			container_of(work, struct slim_device, wd);
-	if (sbdev->notified || !sbdev->dev.driver)
+	struct slim_controller *ctrl = sbdev->ctrl;
+	if (!sbdev->dev.driver)
+		return;
+	/* check if device-up or down needs to be called */
+	mutex_lock(&ctrl->m_ctrl);
+	/* address no longer valid, means device reported absent */
+	for (i = 0; i < ctrl->num_dev; i++) {
+		if (sbdev->laddr == ctrl->addrt[i].laddr &&
+			ctrl->addrt[i].valid == false &&
+			sbdev->notified)
+			break;
+	}
+	mutex_unlock(&ctrl->m_ctrl);
+	sbdrv = to_slim_driver(sbdev->dev.driver);
+	if (i < ctrl->num_dev) {
+		sbdev->notified = false;
+		if (sbdrv->device_down)
+			sbdrv->device_down(sbdev);
+		return;
+	}
+	if (sbdev->notified)
 		return;
 	ret = slim_get_logical_addr(sbdev, sbdev->e_addr, 6, &laddr);
-	sbdrv = to_slim_driver(sbdev->dev.driver);
-	if (!ret && sbdrv->device_up) {
-		sbdev->notified = true;
-		sbdrv->device_up(sbdev);
+	if (!ret) {
+		if (sbdrv)
+			sbdev->notified = true;
+		if (sbdrv->device_up)
+			sbdrv->device_up(sbdev);
 	}
 }
 
@@ -322,7 +343,7 @@
 	INIT_LIST_HEAD(&sbdev->mark_define);
 	INIT_LIST_HEAD(&sbdev->mark_suspend);
 	INIT_LIST_HEAD(&sbdev->mark_removal);
-	INIT_WORK(&sbdev->wd, slim_report_present);
+	INIT_WORK(&sbdev->wd, slim_report);
 	mutex_lock(&ctrl->m_ctrl);
 	list_add_tail(&sbdev->dev_list, &ctrl->devs);
 	mutex_unlock(&ctrl->m_ctrl);
@@ -604,6 +625,31 @@
 EXPORT_SYMBOL_GPL(slim_add_numbered_controller);
 
 /*
+ * slim_report_absent: Controller calls this function when a device
+ *	reports absent, OR when the device cannot be communicated with
+ * @sbdev: Device that cannot be reached, or sent report absent
+ */
+void slim_report_absent(struct slim_device *sbdev)
+{
+	struct slim_controller *ctrl;
+	int i;
+	if (!sbdev)
+		return;
+	ctrl = sbdev->ctrl;
+	if (!ctrl)
+		return;
+	/* invalidate logical addresses */
+	mutex_lock(&ctrl->m_ctrl);
+	for (i = 0; i < ctrl->num_dev; i++) {
+		if (sbdev->laddr == ctrl->addrt[i].laddr)
+			ctrl->addrt[i].valid = false;
+	}
+	mutex_unlock(&ctrl->m_ctrl);
+	queue_work(ctrl->wq, &sbdev->wd);
+}
+EXPORT_SYMBOL(slim_report_absent);
+
+/*
  * slim_msg_response: Deliver Message response received from a device to the
  *	framework.
  * @ctrl: Controller handle
diff --git a/include/linux/slimbus/slimbus.h b/include/linux/slimbus/slimbus.h
index 132135e..cba4394 100644
--- a/include/linux/slimbus/slimbus.h
+++ b/include/linux/slimbus/slimbus.h
@@ -581,6 +581,11 @@
  * @shutdown: Standard shutdown callback used during powerdown/halt.
  * @suspend: Standard suspend callback used during system suspend
  * @resume: Standard resume callback used during system resume
+ * @device_up: This callback is called when the device reports present and
+ *		gets a logical address assigned to it
+ * @device_down: This callback is called when device reports absent, or the
+ *		bus goes down. Device will report present when bus is up and
+ *		device_up callback will be called again when that happens
  * @driver: Slimbus device drivers should initialize name and owner field of
  *	this structure
  * @id_table: List of slimbus devices supported by this driver
@@ -593,6 +598,8 @@
 					pm_message_t pmesg);
 	int				(*resume)(struct slim_device *sldev);
 	int				(*device_up)(struct slim_device *sldev);
+	int				(*device_down)
+						(struct slim_device *sldev);
 
 	struct device_driver		driver;
 	const struct slim_device_id	*id_table;
@@ -1022,6 +1029,13 @@
 				u8 e_len, u8 *laddr, bool valid);
 
 /*
+ * slim_report_absent: Controller calls this function when a device
+ *	reports absent, OR when the device cannot be communicated with
+ * @sbdev: Device that cannot be reached, or that sent report absent
+ */
+void slim_report_absent(struct slim_device *sbdev);
+
+/*
  * slim_msg_response: Deliver Message response received from a device to the
  *	framework.
  * @ctrl: Controller handle