summaryrefslogtreecommitdiff
path: root/drivers/soc/qcom/sysmon.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/soc/qcom/sysmon.c')
-rw-r--r--drivers/soc/qcom/sysmon.c395
1 files changed, 395 insertions, 0 deletions
diff --git a/drivers/soc/qcom/sysmon.c b/drivers/soc/qcom/sysmon.c
new file mode 100644
index 000000000000..f8d0f10b1173
--- /dev/null
+++ b/drivers/soc/qcom/sysmon.c
@@ -0,0 +1,395 @@
+/*
+ * Copyright (c) 2011-2014,2016 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.
+ *
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+#undef DEBUG
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/completion.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <soc/qcom/hsic_sysmon.h>
+#include <soc/qcom/sysmon.h>
+#include <soc/qcom/subsystem_notif.h>
+#include <soc/qcom/smd.h>
+
+#define TX_BUF_SIZE 50
+#define RX_BUF_SIZE 500
+#define TIMEOUT_MS 500
+
+enum transports {
+ TRANSPORT_SMD,
+ TRANSPORT_HSIC,
+};
+
+struct sysmon_subsys {
+ struct mutex lock;
+ struct smd_channel *chan;
+ bool chan_open;
+ struct completion resp_ready;
+ char rx_buf[RX_BUF_SIZE];
+ enum transports transport;
+ struct device *dev;
+ u32 pid;
+ struct list_head list;
+};
+
+static const char *notif_name[SUBSYS_NOTIF_TYPE_COUNT] = {
+ [SUBSYS_BEFORE_SHUTDOWN] = "before_shutdown",
+ [SUBSYS_AFTER_SHUTDOWN] = "after_shutdown",
+ [SUBSYS_BEFORE_POWERUP] = "before_powerup",
+ [SUBSYS_AFTER_POWERUP] = "after_powerup",
+};
+
+static LIST_HEAD(sysmon_list);
+static DEFINE_MUTEX(sysmon_list_lock);
+
+static int sysmon_send_smd(struct sysmon_subsys *ss, const char *tx_buf,
+ size_t len)
+{
+ int ret;
+
+ if (!ss->chan_open)
+ return -ENODEV;
+
+ init_completion(&ss->resp_ready);
+ pr_debug("Sending SMD message: %s\n", tx_buf);
+ smd_write(ss->chan, tx_buf, len);
+ ret = wait_for_completion_timeout(&ss->resp_ready,
+ msecs_to_jiffies(TIMEOUT_MS));
+ if (!ret)
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+static int sysmon_send_hsic(struct sysmon_subsys *ss, const char *tx_buf,
+ size_t len)
+{
+ int ret;
+ size_t actual_len;
+
+ pr_debug("Sending HSIC message: %s\n", tx_buf);
+ ret = hsic_sysmon_write(HSIC_SYSMON_DEV_EXT_MODEM,
+ tx_buf, len, TIMEOUT_MS);
+ if (ret)
+ return ret;
+ ret = hsic_sysmon_read(HSIC_SYSMON_DEV_EXT_MODEM, ss->rx_buf,
+ ARRAY_SIZE(ss->rx_buf), &actual_len, TIMEOUT_MS);
+ return ret;
+}
+
+static int sysmon_send_msg(struct sysmon_subsys *ss, const char *tx_buf,
+ size_t len)
+{
+ int ret;
+
+ switch (ss->transport) {
+ case TRANSPORT_SMD:
+ ret = sysmon_send_smd(ss, tx_buf, len);
+ break;
+ case TRANSPORT_HSIC:
+ ret = sysmon_send_hsic(ss, tx_buf, len);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ if (!ret)
+ pr_debug("Received response: %s\n", ss->rx_buf);
+
+ return ret;
+}
+
+/**
+ * sysmon_send_event_no_qmi() - Notify a subsystem of another's state change
+ * @dest_desc: Subsystem descriptor of the subsystem the notification
+ * should be sent to
+ * @event_desc: Subsystem descriptor of the subsystem that generated the
+ * notification
+ * @notif: ID of the notification type (ex. SUBSYS_BEFORE_SHUTDOWN)
+ *
+ * Returns 0 for success, -EINVAL for invalid destination or notification IDs,
+ * -ENODEV if the transport channel is not open, -ETIMEDOUT if the destination
+ * subsystem does not respond, and -ENOSYS if the destination subsystem
+ * responds, but with something other than an acknowledgement.
+ *
+ * If CONFIG_MSM_SYSMON_COMM is not defined, always return success (0).
+ */
+int sysmon_send_event_no_qmi(struct subsys_desc *dest_desc,
+ struct subsys_desc *event_desc,
+ enum subsys_notif_type notif)
+{
+
+ char tx_buf[TX_BUF_SIZE];
+ int ret;
+ struct sysmon_subsys *tmp, *ss = NULL;
+ const char *event_ss = event_desc->name;
+
+ mutex_lock(&sysmon_list_lock);
+ list_for_each_entry(tmp, &sysmon_list, list)
+ if (tmp->pid == dest_desc->sysmon_pid)
+ ss = tmp;
+ mutex_unlock(&sysmon_list_lock);
+
+ if (ss == NULL)
+ return -EINVAL;
+
+ if (ss->dev == NULL)
+ return -ENODEV;
+
+ if (notif < 0 || notif >= SUBSYS_NOTIF_TYPE_COUNT || event_ss == NULL ||
+ notif_name[notif] == NULL)
+ return -EINVAL;
+
+ snprintf(tx_buf, ARRAY_SIZE(tx_buf), "ssr:%s:%s", event_ss,
+ notif_name[notif]);
+
+ mutex_lock(&ss->lock);
+ ret = sysmon_send_msg(ss, tx_buf, strlen(tx_buf));
+ if (ret) {
+ pr_err("Message sending failed %d\n", ret);
+ goto out;
+ }
+
+ if (strcmp(ss->rx_buf, "ssr:ack")) {
+ pr_debug("Unexpected response %s\n", ss->rx_buf);
+ ret = -ENOSYS;
+ }
+out:
+ mutex_unlock(&ss->lock);
+ return ret;
+}
+EXPORT_SYMBOL(sysmon_send_event_no_qmi);
+
+/**
+ * sysmon_send_shutdown_no_qmi() - send shutdown command to a subsystem.
+ * @dest_desc: Subsystem descriptor of the subsystem to send to
+ *
+ * Returns 0 for success, -EINVAL for an invalid destination, -ENODEV if
+ * the SMD transport channel is not open, -ETIMEDOUT if the destination
+ * subsystem does not respond, and -ENOSYS if the destination subsystem
+ * responds with something unexpected.
+ *
+ * If CONFIG_MSM_SYSMON_COMM is not defined, always return success (0).
+ */
+int sysmon_send_shutdown_no_qmi(struct subsys_desc *dest_desc)
+{
+ struct sysmon_subsys *tmp, *ss = NULL;
+ const char tx_buf[] = "system:shutdown";
+ const char expect[] = "system:ack";
+ int ret;
+
+ mutex_lock(&sysmon_list_lock);
+ list_for_each_entry(tmp, &sysmon_list, list)
+ if (tmp->pid == dest_desc->sysmon_pid)
+ ss = tmp;
+ mutex_unlock(&sysmon_list_lock);
+
+ if (ss == NULL)
+ return -EINVAL;
+
+ if (ss->dev == NULL)
+ return -ENODEV;
+
+ mutex_lock(&ss->lock);
+ ret = sysmon_send_msg(ss, tx_buf, strlen(tx_buf));
+ if (ret) {
+ pr_err("Message sending failed %d\n", ret);
+ goto out;
+ }
+
+ if (strcmp(ss->rx_buf, expect)) {
+ pr_err("Unexpected response %s\n", ss->rx_buf);
+ ret = -ENOSYS;
+ }
+out:
+ mutex_unlock(&ss->lock);
+ return ret;
+}
+EXPORT_SYMBOL(sysmon_send_shutdown_no_qmi);
+
+/**
+ * sysmon_get_reason_no_qmi() - Retrieve failure reason from a subsystem.
+ * @dest_desc: Subsystem descriptor of the subsystem to query
+ * @buf: Caller-allocated buffer for the returned NUL-terminated reason
+ * @len: Length of @buf
+ *
+ * Returns 0 for success, -EINVAL for an invalid destination, -ENODEV if
+ * the SMD transport channel is not open, -ETIMEDOUT if the destination
+ * subsystem does not respond, and -ENOSYS if the destination subsystem
+ * responds with something unexpected.
+ *
+ * If CONFIG_MSM_SYSMON_COMM is not defined, always return success (0).
+ */
+int sysmon_get_reason_no_qmi(struct subsys_desc *dest_desc,
+ char *buf, size_t len)
+{
+ struct sysmon_subsys *tmp, *ss = NULL;
+ const char tx_buf[] = "ssr:retrieve:sfr";
+ const char expect[] = "ssr:return:";
+ size_t prefix_len = ARRAY_SIZE(expect) - 1;
+ int ret;
+
+ mutex_lock(&sysmon_list_lock);
+ list_for_each_entry(tmp, &sysmon_list, list)
+ if (tmp->pid == dest_desc->sysmon_pid)
+ ss = tmp;
+ mutex_unlock(&sysmon_list_lock);
+
+ if (ss == NULL || buf == NULL || len == 0)
+ return -EINVAL;
+
+ if (ss->dev == NULL)
+ return -ENODEV;
+
+ mutex_lock(&ss->lock);
+ ret = sysmon_send_msg(ss, tx_buf, strlen(tx_buf));
+ if (ret) {
+ pr_err("Message sending failed %d\n", ret);
+ goto out;
+ }
+
+ if (strncmp(ss->rx_buf, expect, prefix_len)) {
+ pr_err("Unexpected response %s\n", ss->rx_buf);
+ ret = -ENOSYS;
+ goto out;
+ }
+ strlcpy(buf, ss->rx_buf + prefix_len, len);
+out:
+ mutex_unlock(&ss->lock);
+ return ret;
+}
+EXPORT_SYMBOL(sysmon_get_reason_no_qmi);
+
+static void sysmon_smd_notify(void *priv, unsigned int smd_event)
+{
+ struct sysmon_subsys *ss = priv;
+
+ switch (smd_event) {
+ case SMD_EVENT_DATA: {
+ if (smd_read_avail(ss->chan) > 0) {
+ smd_read_from_cb(ss->chan, ss->rx_buf,
+ ARRAY_SIZE(ss->rx_buf));
+ complete(&ss->resp_ready);
+ }
+ break;
+ }
+ case SMD_EVENT_OPEN:
+ ss->chan_open = true;
+ break;
+ case SMD_EVENT_CLOSE:
+ ss->chan_open = false;
+ break;
+ }
+}
+
+static int sysmon_probe(struct platform_device *pdev)
+{
+ struct sysmon_subsys *ss;
+ int ret;
+
+ if (pdev->id < 0 || pdev->id >= SYSMON_NUM_SS)
+ return -ENODEV;
+
+ ss = devm_kzalloc(&pdev->dev, sizeof(*ss), GFP_KERNEL);
+ if (!ss)
+ return -ENOMEM;
+
+ mutex_init(&ss->lock);
+ if (pdev->id == SYSMON_SS_EXT_MODEM) {
+ ss->transport = TRANSPORT_HSIC;
+ ret = hsic_sysmon_open(HSIC_SYSMON_DEV_EXT_MODEM);
+ if (ret) {
+ pr_err("HSIC open failed\n");
+ return ret;
+ }
+ } else if (pdev->id < SMD_NUM_TYPE) {
+ ss->transport = TRANSPORT_SMD;
+ ret = smd_named_open_on_edge("sys_mon", pdev->id, &ss->chan,
+ ss, sysmon_smd_notify);
+ if (ret) {
+ pr_err("SMD open failed\n");
+ return ret;
+ }
+ smd_disable_read_intr(ss->chan);
+ } else
+ return -EINVAL;
+
+ ss->dev = &pdev->dev;
+ ss->pid = pdev->id;
+
+ mutex_lock(&sysmon_list_lock);
+ INIT_LIST_HEAD(&ss->list);
+ list_add_tail(&ss->list, &sysmon_list);
+ mutex_unlock(&sysmon_list_lock);
+ return 0;
+}
+
+static int sysmon_remove(struct platform_device *pdev)
+{
+ struct sysmon_subsys *sysmon, *tmp, *ss = NULL;
+
+ mutex_lock(&sysmon_list_lock);
+ list_for_each_entry_safe(sysmon, tmp, &sysmon_list, list) {
+ if (sysmon->pid == pdev->id) {
+ ss = sysmon;
+ list_del(&ss->list);
+ }
+ }
+ mutex_unlock(&sysmon_list_lock);
+
+ if (ss == NULL)
+ return -EINVAL;
+
+ mutex_lock(&ss->lock);
+ switch (ss->transport) {
+ case TRANSPORT_SMD:
+ smd_close(ss->chan);
+ break;
+ case TRANSPORT_HSIC:
+ hsic_sysmon_close(HSIC_SYSMON_DEV_EXT_MODEM);
+ break;
+ }
+ mutex_unlock(&ss->lock);
+
+ return 0;
+}
+
+static struct platform_driver sysmon_driver = {
+ .probe = sysmon_probe,
+ .remove = sysmon_remove,
+ .driver = {
+ .name = "sys_mon",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init sysmon_init(void)
+{
+ return platform_driver_register(&sysmon_driver);
+}
+subsys_initcall(sysmon_init);
+
+static void __exit sysmon_exit(void)
+{
+ platform_driver_unregister(&sysmon_driver);
+}
+module_exit(sysmon_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("system monitor communication library");
+MODULE_ALIAS("platform:sys_mon");