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

Commit 513f49ba authored by Chris Lew's avatar Chris Lew
Browse files

soc: qcom: Add snapshot for Glink Probe driver



This snapshot is taken as of msm-4.14 'commit <67942dbf>
("Merge "net: netfilter: IRC DCC for private clients"")'.

In addition change copyrights to SPDX format.

Change-Id: I4ed62a39f1e2f033c5905a9cd2e74704bad4438e
Signed-off-by: default avatarChris Lew <clew@codeaurora.org>
parent 802d671c
Loading
Loading
Loading
Loading
+8 −0
Original line number Original line Diff line number Diff line
@@ -411,4 +411,12 @@ config QCOM_BUS_CONFIG_RPMH
	  from logical nodes to hardware nodes controlled by the BCM (Bus
	  from logical nodes to hardware nodes controlled by the BCM (Bus
	  Clock Manager)
	  Clock Manager)


config QCOM_GLINK
	tristate "GLINK Probe Helper"
	depends on RPMSG_QCOM_GLINK_SMEM
	help
	  This enables the GLINK Probe module. The GLINK RPMSG Plugin is
	  currently designed to plugin with the remote proc framework as a
	  subdev. This module is responsible for creating the glink transports
	  when remote proc is disabled.
endmenu
endmenu
+1 −0
Original line number Original line Diff line number Diff line
@@ -49,3 +49,4 @@ obj-$(CONFIG_QCOM_SDM845_LLCC) += llcc-sdm845.o
CFLAGS_rpmh-rsc.o := -I$(src)
CFLAGS_rpmh-rsc.o := -I$(src)
obj-$(CONFIG_QCOM_BUS_SCALING) += msm_bus/
obj-$(CONFIG_QCOM_BUS_SCALING) += msm_bus/
obj-$(CONFIG_QCOM_FSA4480_I2C) += fsa4480-i2c.o
obj-$(CONFIG_QCOM_FSA4480_I2C) += fsa4480-i2c.o
obj-$(CONFIG_QCOM_GLINK) += glink_probe.o
+431 −0
Original line number Original line Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved.
 */

#include <linux/of.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/platform_device.h>
#include <soc/qcom/subsystem_notif.h>
#include <linux/rpmsg/qcom_glink.h>
#include <linux/rpmsg.h>
#include <linux/ipc_logging.h>

#define GLINK_PROBE_LOG_PAGE_CNT 4
static void *glink_ilc;

#define GLINK_INFO(x, ...)						       \
do {									       \
	if (glink_ilc)							       \
		ipc_log_string(glink_ilc, "[%s]: "x, __func__, ##__VA_ARGS__); \
} while (0)

#define GLINK_ERR(dev, x, ...)						       \
do {									       \
	dev_err(dev, "[%s]: "x, __func__, ##__VA_ARGS__);		       \
	if (glink_ilc)							       \
		ipc_log_string(glink_ilc, "[%s]: "x, __func__, ##__VA_ARGS__); \
} while (0)

#define GLINK_SSR_DO_CLEANUP	0
#define GLINK_SSR_CLEANUP_DONE	1
#define GLINK_SSR_PRIORITY	1
#define GLINK_SSR_REPLY_TIMEOUT	HZ

struct do_cleanup_msg {
	__le32 version;
	__le32 command;
	__le32 seq_num;
	__le32 name_len;
	char name[32];
};

struct cleanup_done_msg {
	__le32 version;
	__le32 response;
	__le32 seq_num;
};

struct glink_ssr_nb {
	struct list_head list;
	struct glink_ssr *ssr;
	void *ssr_register_handle;

	const char *glink_label;
	const char *ssr_label;

	struct notifier_block nb;
};

struct glink_ssr {
	struct device *dev;
	struct rpmsg_endpoint *ept;

	struct list_head notify_list;

	u32 seq_num;
	struct completion completion;
};

struct edge_info {
	struct list_head list;
	struct device *dev;
	struct device_node *node;

	const char *glink_label;
	const char *ssr_label;
	void *glink;

	int (*register_fn)(struct edge_info *einfo);
	void (*unregister_fn)(struct edge_info *einfo);
	struct notifier_block nb;
};
LIST_HEAD(edge_infos);

static int glink_ssr_ssr_cb(struct notifier_block *this,
			    unsigned long code, void *data)
{
	struct glink_ssr_nb *nb = container_of(this, struct glink_ssr_nb, nb);
	struct glink_ssr *ssr = nb->ssr;
	struct device *dev = ssr->dev;
	struct do_cleanup_msg msg;
	int ret;

	if (code == SUBSYS_AFTER_SHUTDOWN) {
		ssr->seq_num++;
		reinit_completion(&ssr->completion);

		memset(&msg, 0, sizeof(msg));
		msg.command = cpu_to_le32(GLINK_SSR_DO_CLEANUP);
		msg.seq_num = cpu_to_le32(ssr->seq_num);
		msg.name_len = cpu_to_le32(strlen(nb->glink_label));
		strlcpy(msg.name, nb->glink_label, sizeof(msg.name));

		GLINK_INFO("%s: notify of %s seq_num:%d\n",
			   dev->parent->of_node->name, nb->glink_label,
			   ssr->seq_num);

		ret = rpmsg_send(ssr->ept, &msg, sizeof(msg));
		if (ret) {
			GLINK_ERR(dev, "fail to send do cleanup to %s %d\n",
				  nb->ssr_label, ret);
			return NOTIFY_DONE;
		}

		ret = wait_for_completion_timeout(&ssr->completion, HZ);
		if (!ret)
			GLINK_ERR(dev, "timeout waiting for cleanup resp\n");
	}
	return NOTIFY_DONE;
}

static int glink_ssr_callback(struct rpmsg_device *rpdev,
			      void *data, int len, void *priv, u32 addr)
{
	struct cleanup_done_msg *msg = data;
	struct glink_ssr *ssr = dev_get_drvdata(&rpdev->dev);

	if (len < sizeof(*msg)) {
		GLINK_ERR(ssr->dev, "message too short\n");
		return -EINVAL;
	}

	if (le32_to_cpu(msg->version) != 0) {
		GLINK_ERR(ssr->dev, "invalid version\n");
		return -EINVAL;
	}

	if (le32_to_cpu(msg->response) != GLINK_SSR_CLEANUP_DONE)
		return 0;

	if (le32_to_cpu(msg->seq_num) != ssr->seq_num) {
		GLINK_ERR(ssr->dev, "invalid response sequence number %d\n",
			  msg->seq_num);
		return -EINVAL;
	}

	complete(&ssr->completion);

	GLINK_INFO("%s: received seq_num:%d\n", ssr->dev->parent->of_node->name,
		   le32_to_cpu(msg->seq_num));

	return 0;
}

static void glink_ssr_init_notify(struct glink_ssr *ssr)
{
	struct device *dev = ssr->dev;
	struct device_node *node;
	struct glink_ssr_nb *nb;
	void *handle;
	int ret;
	int i = 0;

	while (1) {
		node = of_parse_phandle(dev->of_node, "qcom,notify-edges", i++);
		if (!node)
			break;

		nb = devm_kzalloc(dev, sizeof(*nb), GFP_KERNEL);
		if (!nb)
			return;

		ret = of_property_read_string(node, "label", &nb->ssr_label);
		if (ret < 0)
			nb->ssr_label = node->name;

		ret = of_property_read_string(node, "qcom,glink-label",
					      &nb->glink_label);
		if (ret < 0) {
			GLINK_ERR(dev, "no qcom,glink-label for %s\n",
				  nb->ssr_label);
			continue;
		}

		nb->nb.notifier_call = glink_ssr_ssr_cb;
		nb->nb.priority = GLINK_SSR_PRIORITY;

		handle = subsys_notif_register_notifier(nb->ssr_label, &nb->nb);
		if (IS_ERR_OR_NULL(handle)) {
			GLINK_ERR(dev, "register fail for %s SSR notifier\n",
				  nb->ssr_label);
			continue;
		}

		nb->ssr = ssr;
		nb->ssr_register_handle = handle;
		list_add_tail(&nb->list, &ssr->notify_list);
	}
}

static int glink_ssr_probe(struct rpmsg_device *rpdev)
{
	struct glink_ssr *ssr;

	ssr = devm_kzalloc(&rpdev->dev, sizeof(*ssr), GFP_KERNEL);
	if (!ssr)
		return -ENOMEM;

	INIT_LIST_HEAD(&ssr->notify_list);
	init_completion(&ssr->completion);

	ssr->dev = &rpdev->dev;
	ssr->ept = rpdev->ept;

	glink_ssr_init_notify(ssr);

	dev_set_drvdata(&rpdev->dev, ssr);

	return 0;
}

static void glink_ssr_remove(struct rpmsg_device *rpdev)
{
	struct glink_ssr *ssr = dev_get_drvdata(&rpdev->dev);
	struct glink_ssr_nb *nb;

	list_for_each_entry(nb, &ssr->notify_list, list) {
		subsys_notif_unregister_notifier(nb->ssr_register_handle,
						 &nb->nb);
	}

	dev_set_drvdata(&rpdev->dev, NULL);
}

static const struct rpmsg_device_id glink_ssr_match[] = {
	{ "glink_ssr" },
	{}
};

static struct rpmsg_driver glink_ssr_driver = {
	.probe = glink_ssr_probe,
	.remove = glink_ssr_remove,
	.callback = glink_ssr_callback,
	.id_table = glink_ssr_match,
	.drv = {
		.name = "glink_ssr",
	},
};
module_rpmsg_driver(glink_ssr_driver);

static int glink_probe_ssr_cb(struct notifier_block *this,
			      unsigned long code, void *data)
{
	struct edge_info *einfo = container_of(this, struct edge_info, nb);

	GLINK_INFO("received %d for %s", code, einfo->ssr_label);

	switch (code) {
	case SUBSYS_AFTER_POWERUP:
		einfo->register_fn(einfo);
		break;
	case SUBSYS_AFTER_SHUTDOWN:
		einfo->unregister_fn(einfo);
		break;
	default:
		break;
	}

	return NOTIFY_DONE;
}

static int glink_probe_smem_reg(struct edge_info *einfo)
{
	struct device *dev = einfo->dev;

	einfo->glink = qcom_glink_smem_register(dev, einfo->node);
	if (IS_ERR_OR_NULL(einfo->glink)) {
		GLINK_ERR(dev, "register failed for %s\n", einfo->ssr_label);
		einfo->glink = NULL;
	}
	GLINK_INFO("register successful for %s\n", einfo->ssr_label);

	return 0;
}

static void glink_probe_smem_unreg(struct edge_info *einfo)
{
	if (einfo->glink)
		qcom_glink_smem_unregister(einfo->glink);

	einfo->glink = NULL;
	GLINK_INFO("unregister for %s\n", einfo->ssr_label);
}

static int glink_probe_spss_reg(struct edge_info *einfo)
{
	struct device *dev = einfo->dev;

	einfo->glink = qcom_glink_spss_register(dev, einfo->node);
	if (IS_ERR_OR_NULL(einfo->glink)) {
		GLINK_ERR(dev, "register failed for %s\n", einfo->ssr_label);
		einfo->glink = NULL;
	}
	GLINK_INFO("register successful for %s\n", einfo->ssr_label);

	return 0;
}

static void glink_probe_spss_unreg(struct edge_info *einfo)
{
	if (einfo->glink)
		qcom_glink_spss_unregister(einfo->glink);

	einfo->glink = NULL;
	GLINK_INFO("unregister for %s\n", einfo->ssr_label);
}

static void probe_subsystem(struct device *dev, struct device_node *np)
{
	struct edge_info *einfo;
	const char *transport;
	void *handle;
	int ret;

	einfo = devm_kzalloc(dev, sizeof(*einfo), GFP_KERNEL);
	if (!einfo)
		return;

	ret = of_property_read_string(np, "label", &einfo->ssr_label);
	if (ret < 0)
		einfo->ssr_label = np->name;

	ret = of_property_read_string(np, "qcom,glink-label",
				      &einfo->glink_label);
	if (ret < 0) {
		GLINK_ERR(dev, "no qcom,glink-label for %s\n",
			  einfo->ssr_label);
		goto free_einfo;
	}

	einfo->dev = dev;
	einfo->node = np;

	ret = of_property_read_string(np, "transport", &transport);
	if (ret < 0) {
		GLINK_ERR(dev, "%s missing transport\n", einfo->ssr_label);
		goto free_einfo;
	}

	if (!strcmp(transport, "smem")) {
		einfo->register_fn = glink_probe_smem_reg;
		einfo->unregister_fn = glink_probe_smem_unreg;
	} else if (!strcmp(transport, "spss")) {
		einfo->register_fn = glink_probe_spss_reg;
		einfo->unregister_fn = glink_probe_spss_unreg;
	} else if (!strcmp(transport, "spi")) {
		/* SPI SSR is self contained */
		einfo->glink = qcom_glink_spi_register(dev, np);
		if (IS_ERR_OR_NULL(einfo->glink)) {
			GLINK_ERR(dev, "%s failed\n", einfo->ssr_label);
			goto free_einfo;
		}
		list_add_tail(&einfo->list, &edge_infos);
		return;
	}

	einfo->nb.notifier_call = glink_probe_ssr_cb;

	handle = subsys_notif_register_notifier(einfo->ssr_label, &einfo->nb);
	if (IS_ERR_OR_NULL(handle)) {
		GLINK_ERR(dev, "could not register for SSR notifier for %s\n",
			  einfo->ssr_label);
		goto free_einfo;
	}

	list_add_tail(&einfo->list, &edge_infos);
	GLINK_INFO("probe successful for %s\n", einfo->ssr_label);

	return;

free_einfo:
	devm_kfree(dev, einfo);
}

static int glink_probe(struct platform_device *pdev)
{
	struct device_node *pn = pdev->dev.of_node;
	struct device_node *cn;

	for_each_available_child_of_node(pn, cn) {
		probe_subsystem(&pdev->dev, cn);
	}
	return 0;
}

static const struct of_device_id glink_match_table[] = {
	{ .compatible = "qcom,glink" },
	{},
};

static struct platform_driver glink_probe_driver = {
	.probe = glink_probe,
	.driver = {
		.name = "msm_glink",
		.of_match_table = glink_match_table,
	},
};

static int __init glink_probe_init(void)
{
	int ret;

	glink_ilc = ipc_log_context_create(GLINK_PROBE_LOG_PAGE_CNT,
					   "glink_probe", 0);

	ret = platform_driver_register(&glink_probe_driver);
	if (ret) {
		pr_err("%s: glink_probe register failed %d\n",
			__func__, ret);
		return ret;
	}

	return 0;
}
arch_initcall(glink_probe_init);

MODULE_DESCRIPTION("Qualcomm Technologies, Inc. GLINK probe helper driver");
MODULE_LICENSE("GPL v2");