diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index eeb360f19cfb..0c556295a224 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -403,6 +403,25 @@ config QSEE_IPC_IRQ Clients can use this driver to avoid adding common interrupt handling code. +config QCOM_GLINK + tristate "GLINK Probe Helper" + depends on RPMSG_QCOM_GLINK_SMEM + help + This enables the GLINK Probe module. This is designed to set up + other edges in the system. It will initialize all the transports for + all the edges present in the device. + Say M if you want to enable this module. + +config MSM_GLINK_SSR + tristate "MSM GLINK SSR" + depends on RPMSG + help + 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. This + will be used for drivers on MSMs. + Say M if you want to enable this + config MSM_CDSP_LOADER tristate "CDSP loader support" help diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 972a1d4ea896..e46b168011c6 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -45,3 +45,5 @@ obj-$(CONFIG_QMP_DEBUGFS_CLIENT) += qmp-debugfs-client.o obj-$(CONFIG_QCOM_SMP2P_SLEEPSTATE) += smp2p_sleepstate.o obj-$(CONFIG_SENSORS_SSC) += sensors_ssc.o obj-$(CONFIG_QSEE_IPC_IRQ) += qsee_ipc_irq.o +obj-$(CONFIG_QCOM_GLINK) += glink_probe.o +obj-$(CONFIG_MSM_GLINK_SSR) += msm_glink_ssr.o diff --git a/drivers/soc/qcom/glink_probe.c b/drivers/soc/qcom/glink_probe.c new file mode 100644 index 000000000000..03d10c866259 --- /dev/null +++ b/drivers/soc/qcom/glink_probe.c @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GLINK_PROBE_LOG_PAGE_CNT 4 +static void *glink_ilc; + +#define GLINK_INFO(x, ...) ipc_log_string(glink_ilc, x, ##__VA_ARGS__) + +#define GLINK_ERR(dev, x, ...) \ +do { \ + dev_err(dev, "[%s]: "x, __func__, ##__VA_ARGS__); \ + ipc_log_string(glink_ilc, "[%s]: "x, __func__, ##__VA_ARGS__); \ +} while (0) + +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; + void *notifier_handle; +}; +static LIST_HEAD(edge_infos); + +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 %ld for %s\n", 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 void probe_subsystem(struct device *dev, struct device_node *np) +{ + struct edge_info *einfo; + const char *transport; + int ret; + void *handle; + + 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; + } + + 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; + } + einfo->notifier_handle = handle; + + 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 int glink_remove(struct platform_device *pdev) +{ + struct device_node *pn = pdev->dev.of_node; + struct device_node *cn; + struct edge_info *einfo, *tmp; + + for_each_available_child_of_node(pn, cn) { + list_for_each_entry_safe(einfo, tmp, &edge_infos, list) { + subsys_notif_unregister_notifier(einfo->notifier_handle, + &einfo->nb); + list_del(&einfo->list); + } + } + return 0; +} + +static const struct of_device_id glink_match_table[] = { + { .compatible = "qcom,glink" }, + {}, +}; + +static struct platform_driver glink_probe_driver = { + .probe = glink_probe, + .remove = glink_remove, + .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); + +static void __exit glink_probe_exit(void) +{ + if (glink_ilc) + ipc_log_context_destroy(glink_ilc); + + platform_driver_unregister(&glink_probe_driver); +} +module_exit(glink_probe_exit); + +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. GLINK probe helper driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/qcom/msm_glink_ssr.c b/drivers/soc/qcom/msm_glink_ssr.c new file mode 100644 index 000000000000..363dd8eba764 --- /dev/null +++ b/drivers/soc/qcom/msm_glink_ssr.c @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MSM_SSR_LOG_PAGE_CNT 4 +static void *ssr_ilc; + +#define MSM_SSR_INFO(x, ...) ipc_log_string(ssr_ilc, x, ##__VA_ARGS__) + +#define MSM_SSR_ERR(dev, x, ...) \ +do { \ + dev_err(dev, "[%s]: "x, __func__, ##__VA_ARGS__); \ + ipc_log_string(ssr_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; +}; + +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)); + + MSM_SSR_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) { + MSM_SSR_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) + MSM_SSR_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)) { + MSM_SSR_ERR(ssr->dev, "message too short\n"); + return -EINVAL; + } + + if (le32_to_cpu(msg->version) != 0) { + MSM_SSR_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) { + MSM_SSR_ERR(ssr->dev, "invalid response sequence number %d\n", + msg->seq_num); + return -EINVAL; + } + + complete(&ssr->completion); + + MSM_SSR_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; + struct of_phandle_iterator it; + int err; + + of_for_each_phandle(&it, err, dev->of_node, "qcom,notify-edges", + NULL, 0) { + node = it.node; + + nb = devm_kzalloc(dev, sizeof(*nb), GFP_KERNEL); + if (!nb) { + of_node_put(node); + 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) { + MSM_SSR_ERR(dev, "no qcom,glink-label for %s\n", + nb->ssr_label); + of_node_put(node); + continue; + } + + nb->nb.notifier_call = glink_ssr_ssr_cb; + nb->nb.priority = GLINK_SSR_PRIORITY; + nb->ssr = ssr; + + handle = subsys_notif_register_notifier(nb->ssr_label, &nb->nb); + if (IS_ERR_OR_NULL(handle)) { + MSM_SSR_ERR(dev, "register fail for %s SSR notifier\n", + nb->ssr_label); + of_node_put(node); + continue; + } + + 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; + + ssr_ilc = ipc_log_context_create(MSM_SSR_LOG_PAGE_CNT, + "glink_ssr", 0); + + 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 = "msm_glink_ssr", + }, +}; +module_rpmsg_driver(glink_ssr_driver); + +MODULE_ALIAS("rpmsg:msm_glink_ssr"); +MODULE_DESCRIPTION("MSM GLINK SSR notifier"); +MODULE_LICENSE("GPL v2");