summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2017-09-10 00:30:50 +0300
committerLinus Torvalds <torvalds@linux-foundation.org>2017-09-10 00:30:50 +0300
commitd7efc352abb8903ccb8600e1148f59dd9164317e (patch)
treea13453b78a8fadb620f731930bfcaefb3d722535 /drivers
parent8c1d70b2de517e7b44dcac24416e60c9662db507 (diff)
parent7c89717f82bd305e3102963485f3da160d11bcf6 (diff)
downloadlinux-d7efc352abb8903ccb8600e1148f59dd9164317e.tar.xz
Merge tag 'rproc-v4.14' of git://github.com/andersson/remoteproc
Pull remoteproc updates from Bjorn Andersson: "This adds and improves remoteproc support for TI DA8xx/OMAP-L13x DSP, TI Keystone 66AK2G DSP and iMX6SX/7D Cortex M4 coprocessors. It introduces the Qualcomm restart notifier and a few fixes" * tag 'rproc-v4.14' of git://github.com/andersson/remoteproc: remoteproc: Introduce rproc handle accessor for children remoteproc: qcom: Make ssr_notifiers local remoteproc: Stop subdevices in reverse order remoteproc: imx_rproc: add a NXP/Freescale imx_rproc driver remoteproc: dt: Provide bindings for iMX6SX/7D Remote Processor Controller driver remoteproc: qcom: Use PTR_ERR_OR_ZERO remoteproc: st: explicitly request exclusive reset control remoteproc: qcom: explicitly request exclusive reset control remoteproc/keystone: explicitly request exclusive reset control remoteproc/keystone: Add support for Keystone 66AK2G SOCs remoteproc/davinci: Add device tree support for OMAP-L138 DSP dt-bindings: remoteproc: Add bindings for Davinci DSP processors remoteproc/davinci: Add support to parse internal memories remoteproc/davinci: Switch to platform_get_resource_byname() remoteproc: make device_type const soc: qcom: GLINK SSR notifier remoteproc: qcom: Add support for SSR notifications remoteproc: Merge __rproc_boot() with rproc_boot()
Diffstat (limited to 'drivers')
-rw-r--r--drivers/remoteproc/Kconfig9
-rw-r--r--drivers/remoteproc/Makefile1
-rw-r--r--drivers/remoteproc/da8xx_remoteproc.c98
-rw-r--r--drivers/remoteproc/imx_rproc.c426
-rw-r--r--drivers/remoteproc/keystone_remoteproc.c3
-rw-r--r--drivers/remoteproc/qcom_adsp_pil.c11
-rw-r--r--drivers/remoteproc/qcom_common.c73
-rw-r--r--drivers/remoteproc/qcom_common.h10
-rw-r--r--drivers/remoteproc/qcom_q6v5_pil.c6
-rw-r--r--drivers/remoteproc/remoteproc_core.c35
-rw-r--r--drivers/remoteproc/remoteproc_internal.h1
-rw-r--r--drivers/remoteproc/st_remoteproc.c6
-rw-r--r--drivers/soc/qcom/Kconfig9
-rw-r--r--drivers/soc/qcom/Makefile1
-rw-r--r--drivers/soc/qcom/glink_ssr.c164
15 files changed, 825 insertions, 28 deletions
diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
index 8891a8e50f12..79f903af6504 100644
--- a/drivers/remoteproc/Kconfig
+++ b/drivers/remoteproc/Kconfig
@@ -12,6 +12,15 @@ config REMOTEPROC
if REMOTEPROC
+config IMX_REMOTEPROC
+ tristate "IMX6/7 remoteproc support"
+ depends on SOC_IMX6SX || SOC_IMX7D
+ help
+ Say y here to support iMX's remote processors (Cortex M4
+ on iMX7D) via the remote processor framework.
+
+ It's safe to say N here.
+
config OMAP_REMOTEPROC
tristate "OMAP remoteproc support"
depends on HAS_DMA
diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
index f1ce5fc8a2f3..1a0b3dd44b8c 100644
--- a/drivers/remoteproc/Makefile
+++ b/drivers/remoteproc/Makefile
@@ -8,6 +8,7 @@ remoteproc-y += remoteproc_debugfs.o
remoteproc-y += remoteproc_sysfs.o
remoteproc-y += remoteproc_virtio.o
remoteproc-y += remoteproc_elf_loader.o
+obj-$(CONFIG_IMX_REMOTEPROC) += imx_rproc.o
obj-$(CONFIG_OMAP_REMOTEPROC) += omap_remoteproc.o
obj-$(CONFIG_WKUP_M3_RPROC) += wkup_m3_rproc.o
obj-$(CONFIG_DA8XX_REMOTEPROC) += da8xx_remoteproc.o
diff --git a/drivers/remoteproc/da8xx_remoteproc.c b/drivers/remoteproc/da8xx_remoteproc.c
index 99539cec1329..bf3b9034c319 100644
--- a/drivers/remoteproc/da8xx_remoteproc.c
+++ b/drivers/remoteproc/da8xx_remoteproc.c
@@ -16,6 +16,7 @@
#include <linux/irq.h>
#include <linux/kernel.h>
#include <linux/module.h>
+#include <linux/of_reserved_mem.h>
#include <linux/platform_device.h>
#include <linux/remoteproc.h>
@@ -38,9 +39,27 @@ MODULE_PARM_DESC(da8xx_fw_name,
#define SYSCFG_CHIPSIG3 BIT(3)
#define SYSCFG_CHIPSIG4 BIT(4)
+#define DA8XX_RPROC_LOCAL_ADDRESS_MASK (SZ_16M - 1)
+
+/**
+ * struct da8xx_rproc_mem - internal memory structure
+ * @cpu_addr: MPU virtual address of the memory region
+ * @bus_addr: Bus address used to access the memory region
+ * @dev_addr: Device address of the memory region from DSP view
+ * @size: Size of the memory region
+ */
+struct da8xx_rproc_mem {
+ void __iomem *cpu_addr;
+ phys_addr_t bus_addr;
+ u32 dev_addr;
+ size_t size;
+};
+
/**
* struct da8xx_rproc - da8xx remote processor instance state
* @rproc: rproc handle
+ * @mem: internal memory regions data
+ * @num_mems: number of internal memory regions
* @dsp_clk: placeholder for platform's DSP clk
* @ack_fxn: chip-specific ack function for ack'ing irq
* @irq_data: ack_fxn function parameter
@@ -50,6 +69,8 @@ MODULE_PARM_DESC(da8xx_fw_name,
*/
struct da8xx_rproc {
struct rproc *rproc;
+ struct da8xx_rproc_mem *mem;
+ int num_mems;
struct clk *dsp_clk;
void (*ack_fxn)(struct irq_data *data);
struct irq_data *irq_data;
@@ -158,6 +179,44 @@ static const struct rproc_ops da8xx_rproc_ops = {
.kick = da8xx_rproc_kick,
};
+static int da8xx_rproc_get_internal_memories(struct platform_device *pdev,
+ struct da8xx_rproc *drproc)
+{
+ static const char * const mem_names[] = {"l2sram", "l1pram", "l1dram"};
+ int num_mems = ARRAY_SIZE(mem_names);
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ int i;
+
+ drproc->mem = devm_kcalloc(dev, num_mems, sizeof(*drproc->mem),
+ GFP_KERNEL);
+ if (!drproc->mem)
+ return -ENOMEM;
+
+ for (i = 0; i < num_mems; i++) {
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ mem_names[i]);
+ drproc->mem[i].cpu_addr = devm_ioremap_resource(dev, res);
+ if (IS_ERR(drproc->mem[i].cpu_addr)) {
+ dev_err(dev, "failed to parse and map %s memory\n",
+ mem_names[i]);
+ return PTR_ERR(drproc->mem[i].cpu_addr);
+ }
+ drproc->mem[i].bus_addr = res->start;
+ drproc->mem[i].dev_addr =
+ res->start & DA8XX_RPROC_LOCAL_ADDRESS_MASK;
+ drproc->mem[i].size = resource_size(res);
+
+ dev_dbg(dev, "memory %8s: bus addr %pa size 0x%x va %p da 0x%x\n",
+ mem_names[i], &drproc->mem[i].bus_addr,
+ drproc->mem[i].size, drproc->mem[i].cpu_addr,
+ drproc->mem[i].dev_addr);
+ }
+ drproc->num_mems = num_mems;
+
+ return 0;
+}
+
static int da8xx_rproc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -184,12 +243,14 @@ static int da8xx_rproc_probe(struct platform_device *pdev)
return -EINVAL;
}
- bootreg_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ bootreg_res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "host1cfg");
bootreg = devm_ioremap_resource(dev, bootreg_res);
if (IS_ERR(bootreg))
return PTR_ERR(bootreg);
- chipsig_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ chipsig_res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "chipsig");
chipsig = devm_ioremap_resource(dev, chipsig_res);
if (IS_ERR(chipsig))
return PTR_ERR(chipsig);
@@ -201,16 +262,31 @@ static int da8xx_rproc_probe(struct platform_device *pdev)
return PTR_ERR(dsp_clk);
}
+ if (dev->of_node) {
+ ret = of_reserved_mem_device_init(dev);
+ if (ret) {
+ dev_err(dev, "device does not have specific CMA pool: %d\n",
+ ret);
+ return ret;
+ }
+ }
+
rproc = rproc_alloc(dev, "dsp", &da8xx_rproc_ops, da8xx_fw_name,
sizeof(*drproc));
- if (!rproc)
- return -ENOMEM;
+ if (!rproc) {
+ ret = -ENOMEM;
+ goto free_mem;
+ }
drproc = rproc->priv;
drproc->rproc = rproc;
drproc->dsp_clk = dsp_clk;
rproc->has_iommu = false;
+ ret = da8xx_rproc_get_internal_memories(pdev, drproc);
+ if (ret)
+ goto free_rproc;
+
platform_set_drvdata(pdev, rproc);
/* everything the ISR needs is now setup, so hook it up */
@@ -247,7 +323,9 @@ static int da8xx_rproc_probe(struct platform_device *pdev)
free_rproc:
rproc_free(rproc);
-
+free_mem:
+ if (dev->of_node)
+ of_reserved_mem_device_release(dev);
return ret;
}
@@ -255,6 +333,7 @@ static int da8xx_rproc_remove(struct platform_device *pdev)
{
struct rproc *rproc = platform_get_drvdata(pdev);
struct da8xx_rproc *drproc = (struct da8xx_rproc *)rproc->priv;
+ struct device *dev = &pdev->dev;
/*
* The devm subsystem might end up releasing things before
@@ -265,15 +344,24 @@ static int da8xx_rproc_remove(struct platform_device *pdev)
rproc_del(rproc);
rproc_free(rproc);
+ if (dev->of_node)
+ of_reserved_mem_device_release(dev);
return 0;
}
+static const struct of_device_id davinci_rproc_of_match[] __maybe_unused = {
+ { .compatible = "ti,da850-dsp", },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, davinci_rproc_of_match);
+
static struct platform_driver da8xx_rproc_driver = {
.probe = da8xx_rproc_probe,
.remove = da8xx_rproc_remove,
.driver = {
.name = "davinci-rproc",
+ .of_match_table = of_match_ptr(davinci_rproc_of_match),
},
};
diff --git a/drivers/remoteproc/imx_rproc.c b/drivers/remoteproc/imx_rproc.c
new file mode 100644
index 000000000000..612d91403341
--- /dev/null
+++ b/drivers/remoteproc/imx_rproc.c
@@ -0,0 +1,426 @@
+/*
+ * Copyright (c) 2017 Pengutronix, Oleksij Rempel <kernel@pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ */
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/remoteproc.h>
+
+#define IMX7D_SRC_SCR 0x0C
+#define IMX7D_ENABLE_M4 BIT(3)
+#define IMX7D_SW_M4P_RST BIT(2)
+#define IMX7D_SW_M4C_RST BIT(1)
+#define IMX7D_SW_M4C_NON_SCLR_RST BIT(0)
+
+#define IMX7D_M4_RST_MASK (IMX7D_ENABLE_M4 | IMX7D_SW_M4P_RST \
+ | IMX7D_SW_M4C_RST \
+ | IMX7D_SW_M4C_NON_SCLR_RST)
+
+#define IMX7D_M4_START (IMX7D_ENABLE_M4 | IMX7D_SW_M4P_RST \
+ | IMX7D_SW_M4C_RST)
+#define IMX7D_M4_STOP IMX7D_SW_M4C_NON_SCLR_RST
+
+/* Address: 0x020D8000 */
+#define IMX6SX_SRC_SCR 0x00
+#define IMX6SX_ENABLE_M4 BIT(22)
+#define IMX6SX_SW_M4P_RST BIT(12)
+#define IMX6SX_SW_M4C_NON_SCLR_RST BIT(4)
+#define IMX6SX_SW_M4C_RST BIT(3)
+
+#define IMX6SX_M4_START (IMX6SX_ENABLE_M4 | IMX6SX_SW_M4P_RST \
+ | IMX6SX_SW_M4C_RST)
+#define IMX6SX_M4_STOP IMX6SX_SW_M4C_NON_SCLR_RST
+#define IMX6SX_M4_RST_MASK (IMX6SX_ENABLE_M4 | IMX6SX_SW_M4P_RST \
+ | IMX6SX_SW_M4C_NON_SCLR_RST \
+ | IMX6SX_SW_M4C_RST)
+
+#define IMX7D_RPROC_MEM_MAX 8
+
+/**
+ * struct imx_rproc_mem - slim internal memory structure
+ * @cpu_addr: MPU virtual address of the memory region
+ * @sys_addr: Bus address used to access the memory region
+ * @size: Size of the memory region
+ */
+struct imx_rproc_mem {
+ void __iomem *cpu_addr;
+ phys_addr_t sys_addr;
+ size_t size;
+};
+
+/* att flags */
+/* M4 own area. Can be mapped at probe */
+#define ATT_OWN BIT(1)
+
+/* address translation table */
+struct imx_rproc_att {
+ u32 da; /* device address (From Cortex M4 view)*/
+ u32 sa; /* system bus address */
+ u32 size; /* size of reg range */
+ int flags;
+};
+
+struct imx_rproc_dcfg {
+ u32 src_reg;
+ u32 src_mask;
+ u32 src_start;
+ u32 src_stop;
+ const struct imx_rproc_att *att;
+ size_t att_size;
+};
+
+struct imx_rproc {
+ struct device *dev;
+ struct regmap *regmap;
+ struct rproc *rproc;
+ const struct imx_rproc_dcfg *dcfg;
+ struct imx_rproc_mem mem[IMX7D_RPROC_MEM_MAX];
+ struct clk *clk;
+};
+
+static const struct imx_rproc_att imx_rproc_att_imx7d[] = {
+ /* dev addr , sys addr , size , flags */
+ /* OCRAM_S (M4 Boot code) - alias */
+ { 0x00000000, 0x00180000, 0x00008000, 0 },
+ /* OCRAM_S (Code) */
+ { 0x00180000, 0x00180000, 0x00008000, ATT_OWN },
+ /* OCRAM (Code) - alias */
+ { 0x00900000, 0x00900000, 0x00020000, 0 },
+ /* OCRAM_EPDC (Code) - alias */
+ { 0x00920000, 0x00920000, 0x00020000, 0 },
+ /* OCRAM_PXP (Code) - alias */
+ { 0x00940000, 0x00940000, 0x00008000, 0 },
+ /* TCML (Code) */
+ { 0x1FFF8000, 0x007F8000, 0x00008000, ATT_OWN },
+ /* DDR (Code) - alias, first part of DDR (Data) */
+ { 0x10000000, 0x80000000, 0x0FFF0000, 0 },
+
+ /* TCMU (Data) */
+ { 0x20000000, 0x00800000, 0x00008000, ATT_OWN },
+ /* OCRAM (Data) */
+ { 0x20200000, 0x00900000, 0x00020000, 0 },
+ /* OCRAM_EPDC (Data) */
+ { 0x20220000, 0x00920000, 0x00020000, 0 },
+ /* OCRAM_PXP (Data) */
+ { 0x20240000, 0x00940000, 0x00008000, 0 },
+ /* DDR (Data) */
+ { 0x80000000, 0x80000000, 0x60000000, 0 },
+};
+
+static const struct imx_rproc_att imx_rproc_att_imx6sx[] = {
+ /* dev addr , sys addr , size , flags */
+ /* TCML (M4 Boot Code) - alias */
+ { 0x00000000, 0x007F8000, 0x00008000, 0 },
+ /* OCRAM_S (Code) */
+ { 0x00180000, 0x008F8000, 0x00004000, 0 },
+ /* OCRAM_S (Code) - alias */
+ { 0x00180000, 0x008FC000, 0x00004000, 0 },
+ /* TCML (Code) */
+ { 0x1FFF8000, 0x007F8000, 0x00008000, ATT_OWN },
+ /* DDR (Code) - alias, first part of DDR (Data) */
+ { 0x10000000, 0x80000000, 0x0FFF8000, 0 },
+
+ /* TCMU (Data) */
+ { 0x20000000, 0x00800000, 0x00008000, ATT_OWN },
+ /* OCRAM_S (Data) - alias? */
+ { 0x208F8000, 0x008F8000, 0x00004000, 0 },
+ /* DDR (Data) */
+ { 0x80000000, 0x80000000, 0x60000000, 0 },
+};
+
+static const struct imx_rproc_dcfg imx_rproc_cfg_imx7d = {
+ .src_reg = IMX7D_SRC_SCR,
+ .src_mask = IMX7D_M4_RST_MASK,
+ .src_start = IMX7D_M4_START,
+ .src_stop = IMX7D_M4_STOP,
+ .att = imx_rproc_att_imx7d,
+ .att_size = ARRAY_SIZE(imx_rproc_att_imx7d),
+};
+
+static const struct imx_rproc_dcfg imx_rproc_cfg_imx6sx = {
+ .src_reg = IMX6SX_SRC_SCR,
+ .src_mask = IMX6SX_M4_RST_MASK,
+ .src_start = IMX6SX_M4_START,
+ .src_stop = IMX6SX_M4_STOP,
+ .att = imx_rproc_att_imx6sx,
+ .att_size = ARRAY_SIZE(imx_rproc_att_imx6sx),
+};
+
+static int imx_rproc_start(struct rproc *rproc)
+{
+ struct imx_rproc *priv = rproc->priv;
+ const struct imx_rproc_dcfg *dcfg = priv->dcfg;
+ struct device *dev = priv->dev;
+ int ret;
+
+ ret = regmap_update_bits(priv->regmap, dcfg->src_reg,
+ dcfg->src_mask, dcfg->src_start);
+ if (ret)
+ dev_err(dev, "Filed to enable M4!\n");
+
+ return ret;
+}
+
+static int imx_rproc_stop(struct rproc *rproc)
+{
+ struct imx_rproc *priv = rproc->priv;
+ const struct imx_rproc_dcfg *dcfg = priv->dcfg;
+ struct device *dev = priv->dev;
+ int ret;
+
+ ret = regmap_update_bits(priv->regmap, dcfg->src_reg,
+ dcfg->src_mask, dcfg->src_stop);
+ if (ret)
+ dev_err(dev, "Filed to stop M4!\n");
+
+ return ret;
+}
+
+static int imx_rproc_da_to_sys(struct imx_rproc *priv, u64 da,
+ int len, u64 *sys)
+{
+ const struct imx_rproc_dcfg *dcfg = priv->dcfg;
+ int i;
+
+ /* parse address translation table */
+ for (i = 0; i < dcfg->att_size; i++) {
+ const struct imx_rproc_att *att = &dcfg->att[i];
+
+ if (da >= att->da && da + len < att->da + att->size) {
+ unsigned int offset = da - att->da;
+
+ *sys = att->sa + offset;
+ return 0;
+ }
+ }
+
+ dev_warn(priv->dev, "Translation filed: da = 0x%llx len = 0x%x\n",
+ da, len);
+ return -ENOENT;
+}
+
+static void *imx_rproc_da_to_va(struct rproc *rproc, u64 da, int len)
+{
+ struct imx_rproc *priv = rproc->priv;
+ void *va = NULL;
+ u64 sys;
+ int i;
+
+ if (len <= 0)
+ return NULL;
+
+ /*
+ * On device side we have many aliases, so we need to convert device
+ * address (M4) to system bus address first.
+ */
+ if (imx_rproc_da_to_sys(priv, da, len, &sys))
+ return NULL;
+
+ for (i = 0; i < IMX7D_RPROC_MEM_MAX; i++) {
+ if (sys >= priv->mem[i].sys_addr && sys + len <
+ priv->mem[i].sys_addr + priv->mem[i].size) {
+ unsigned int offset = sys - priv->mem[i].sys_addr;
+ /* __force to make sparse happy with type conversion */
+ va = (__force void *)(priv->mem[i].cpu_addr + offset);
+ break;
+ }
+ }
+
+ dev_dbg(&rproc->dev, "da = 0x%llx len = 0x%x va = 0x%p\n", da, len, va);
+
+ return va;
+}
+
+static const struct rproc_ops imx_rproc_ops = {
+ .start = imx_rproc_start,
+ .stop = imx_rproc_stop,
+ .da_to_va = imx_rproc_da_to_va,
+};
+
+static int imx_rproc_addr_init(struct imx_rproc *priv,
+ struct platform_device *pdev)
+{
+ const struct imx_rproc_dcfg *dcfg = priv->dcfg;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ int a, b = 0, err, nph;
+
+ /* remap required addresses */
+ for (a = 0; a < dcfg->att_size; a++) {
+ const struct imx_rproc_att *att = &dcfg->att[a];
+
+ if (!(att->flags & ATT_OWN))
+ continue;
+
+ if (b > IMX7D_RPROC_MEM_MAX)
+ break;
+
+ priv->mem[b].cpu_addr = devm_ioremap(&pdev->dev,
+ att->sa, att->size);
+ if (IS_ERR(priv->mem[b].cpu_addr)) {
+ dev_err(dev, "devm_ioremap_resource failed\n");
+ err = PTR_ERR(priv->mem[b].cpu_addr);
+ return err;
+ }
+ priv->mem[b].sys_addr = att->sa;
+ priv->mem[b].size = att->size;
+ b++;
+ }
+
+ /* memory-region is optional property */
+ nph = of_count_phandle_with_args(np, "memory-region", NULL);
+ if (nph <= 0)
+ return 0;
+
+ /* remap optional addresses */
+ for (a = 0; a < nph; a++) {
+ struct device_node *node;
+ struct resource res;
+
+ node = of_parse_phandle(np, "memory-region", a);
+ err = of_address_to_resource(node, 0, &res);
+ if (err) {
+ dev_err(dev, "unable to resolve memory region\n");
+ return err;
+ }
+
+ if (b > IMX7D_RPROC_MEM_MAX)
+ break;
+
+ priv->mem[b].cpu_addr = devm_ioremap_resource(&pdev->dev, &res);
+ if (IS_ERR(priv->mem[b].cpu_addr)) {
+ dev_err(dev, "devm_ioremap_resource failed\n");
+ err = PTR_ERR(priv->mem[b].cpu_addr);
+ return err;
+ }
+ priv->mem[b].sys_addr = res.start;
+ priv->mem[b].size = resource_size(&res);
+ b++;
+ }
+
+ return 0;
+}
+
+static int imx_rproc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct imx_rproc *priv;
+ struct rproc *rproc;
+ struct regmap_config config = { .name = "imx-rproc" };
+ const struct imx_rproc_dcfg *dcfg;
+ struct regmap *regmap;
+ int ret;
+
+ regmap = syscon_regmap_lookup_by_phandle(np, "syscon");
+ if (IS_ERR(regmap)) {
+ dev_err(dev, "failed to find syscon\n");
+ return PTR_ERR(regmap);
+ }
+ regmap_attach_dev(dev, regmap, &config);
+
+ /* set some other name then imx */
+ rproc = rproc_alloc(dev, "imx-rproc", &imx_rproc_ops,
+ NULL, sizeof(*priv));
+ if (!rproc) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ dcfg = of_device_get_match_data(dev);
+ if (!dcfg)
+ return -EINVAL;
+
+ priv = rproc->priv;
+ priv->rproc = rproc;
+ priv->regmap = regmap;
+ priv->dcfg = dcfg;
+ priv->dev = dev;
+
+ dev_set_drvdata(dev, rproc);
+
+ ret = imx_rproc_addr_init(priv, pdev);
+ if (ret) {
+ dev_err(dev, "filed on imx_rproc_addr_init\n");
+ goto err_put_rproc;
+ }
+
+ priv->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(priv->clk)) {
+ dev_err(dev, "Failed to get clock\n");
+ rproc_free(rproc);
+ return PTR_ERR(priv->clk);
+ }
+
+ /*
+ * clk for M4 block including memory. Should be
+ * enabled before .start for FW transfer.
+ */
+ ret = clk_prepare_enable(priv->clk);
+ if (ret) {
+ dev_err(&rproc->dev, "Failed to enable clock\n");
+ rproc_free(rproc);
+ return ret;
+ }
+
+ ret = rproc_add(rproc);
+ if (ret) {
+ dev_err(dev, "rproc_add failed\n");
+ goto err_put_clk;
+ }
+
+ return ret;
+
+err_put_clk:
+ clk_disable_unprepare(priv->clk);
+err_put_rproc:
+ rproc_free(rproc);
+err:
+ return ret;
+}
+
+static int imx_rproc_remove(struct platform_device *pdev)
+{
+ struct rproc *rproc = platform_get_drvdata(pdev);
+ struct imx_rproc *priv = rproc->priv;
+
+ clk_disable_unprepare(priv->clk);
+ rproc_del(rproc);
+ rproc_free(rproc);
+
+ return 0;
+}
+
+static const struct of_device_id imx_rproc_of_match[] = {
+ { .compatible = "fsl,imx7d-cm4", .data = &imx_rproc_cfg_imx7d },
+ { .compatible = "fsl,imx6sx-cm4", .data = &imx_rproc_cfg_imx6sx },
+ {},
+};
+MODULE_DEVICE_TABLE(of, imx_rproc_of_match);
+
+static struct platform_driver imx_rproc_driver = {
+ .probe = imx_rproc_probe,
+ .remove = imx_rproc_remove,
+ .driver = {
+ .name = "imx-rproc",
+ .of_match_table = imx_rproc_of_match,
+ },
+};
+
+module_platform_driver(imx_rproc_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("IMX6SX/7D remote processor control driver");
+MODULE_AUTHOR("Oleksij Rempel <o.rempel@pengutronix.de>");
diff --git a/drivers/remoteproc/keystone_remoteproc.c b/drivers/remoteproc/keystone_remoteproc.c
index 5f776bfd674a..aaac31134e39 100644
--- a/drivers/remoteproc/keystone_remoteproc.c
+++ b/drivers/remoteproc/keystone_remoteproc.c
@@ -410,7 +410,7 @@ static int keystone_rproc_probe(struct platform_device *pdev)
if (ret)
goto free_rproc;
- ksproc->reset = devm_reset_control_get(dev, NULL);
+ ksproc->reset = devm_reset_control_get_exclusive(dev, NULL);
if (IS_ERR(ksproc->reset)) {
ret = PTR_ERR(ksproc->reset);
goto free_rproc;
@@ -505,6 +505,7 @@ static const struct of_device_id keystone_rproc_of_match[] = {
{ .compatible = "ti,k2hk-dsp", },
{ .compatible = "ti,k2l-dsp", },
{ .compatible = "ti,k2e-dsp", },
+ { .compatible = "ti,k2g-dsp", },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, keystone_rproc_of_match);
diff --git a/drivers/remoteproc/qcom_adsp_pil.c b/drivers/remoteproc/qcom_adsp_pil.c
index 49fe2f807e1d..d01a8daf948a 100644
--- a/drivers/remoteproc/qcom_adsp_pil.c
+++ b/drivers/remoteproc/qcom_adsp_pil.c
@@ -38,6 +38,7 @@ struct adsp_data {
const char *firmware_name;
int pas_id;
bool has_aggre2_clk;
+ const char *ssr_name;
};
struct qcom_adsp {
@@ -72,6 +73,7 @@ struct qcom_adsp {
size_t mem_size;
struct qcom_rproc_subdev smd_subdev;
+ struct qcom_rproc_ssr ssr_subdev;
};
static int adsp_load(struct rproc *rproc, const struct firmware *fw)
@@ -266,10 +268,7 @@ static int adsp_init_regulator(struct qcom_adsp *adsp)
regulator_set_load(adsp->cx_supply, 100000);
adsp->px_supply = devm_regulator_get(adsp->dev, "px");
- if (IS_ERR(adsp->px_supply))
- return PTR_ERR(adsp->px_supply);
-
- return 0;
+ return PTR_ERR_OR_ZERO(adsp->px_supply);
}
static int adsp_request_irq(struct qcom_adsp *adsp,
@@ -402,6 +401,7 @@ static int adsp_probe(struct platform_device *pdev)
}
qcom_add_smd_subdev(rproc, &adsp->smd_subdev);
+ qcom_add_ssr_subdev(rproc, &adsp->ssr_subdev, desc->ssr_name);
ret = rproc_add(rproc);
if (ret)
@@ -423,6 +423,7 @@ static int adsp_remove(struct platform_device *pdev)
rproc_del(adsp->rproc);
qcom_remove_smd_subdev(adsp->rproc, &adsp->smd_subdev);
+ qcom_remove_ssr_subdev(adsp->rproc, &adsp->ssr_subdev);
rproc_free(adsp->rproc);
return 0;
@@ -433,6 +434,7 @@ static const struct adsp_data adsp_resource_init = {
.firmware_name = "adsp.mdt",
.pas_id = 1,
.has_aggre2_clk = false,
+ .ssr_name = "lpass",
};
static const struct adsp_data slpi_resource_init = {
@@ -440,6 +442,7 @@ static const struct adsp_data slpi_resource_init = {
.firmware_name = "slpi.mdt",
.pas_id = 12,
.has_aggre2_clk = true,
+ .ssr_name = "dsps",
};
static const struct of_device_id adsp_of_match[] = {
diff --git a/drivers/remoteproc/qcom_common.c b/drivers/remoteproc/qcom_common.c
index bb90481215c6..257680f82b27 100644
--- a/drivers/remoteproc/qcom_common.c
+++ b/drivers/remoteproc/qcom_common.c
@@ -18,6 +18,7 @@
#include <linux/firmware.h>
#include <linux/kernel.h>
#include <linux/module.h>
+#include <linux/notifier.h>
#include <linux/remoteproc.h>
#include <linux/rpmsg/qcom_smd.h>
@@ -25,6 +26,9 @@
#include "qcom_common.h"
#define to_smd_subdev(d) container_of(d, struct qcom_rproc_subdev, subdev)
+#define to_ssr_subdev(d) container_of(d, struct qcom_rproc_ssr, subdev)
+
+static BLOCKING_NOTIFIER_HEAD(ssr_notifiers);
/**
* qcom_mdt_find_rsc_table() - provide dummy resource table for remoteproc
@@ -51,7 +55,7 @@ static int smd_subdev_probe(struct rproc_subdev *subdev)
smd->edge = qcom_smd_register_edge(smd->dev, smd->node);
- return IS_ERR(smd->edge) ? PTR_ERR(smd->edge) : 0;
+ return PTR_ERR_OR_ZERO(smd->edge);
}
static void smd_subdev_remove(struct rproc_subdev *subdev)
@@ -92,5 +96,72 @@ void qcom_remove_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd)
}
EXPORT_SYMBOL_GPL(qcom_remove_smd_subdev);
+/**
+ * qcom_register_ssr_notifier() - register SSR notification handler
+ * @nb: notifier_block to notify for restart notifications
+ *
+ * Returns 0 on success, negative errno on failure.
+ *
+ * This register the @notify function as handler for restart notifications. As
+ * remote processors are stopped this function will be called, with the SSR
+ * name passed as a parameter.
+ */
+int qcom_register_ssr_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_register(&ssr_notifiers, nb);
+}
+EXPORT_SYMBOL_GPL(qcom_register_ssr_notifier);
+
+/**
+ * qcom_unregister_ssr_notifier() - unregister SSR notification handler
+ * @nb: notifier_block to unregister
+ */
+void qcom_unregister_ssr_notifier(struct notifier_block *nb)
+{
+ blocking_notifier_chain_unregister(&ssr_notifiers, nb);
+}
+EXPORT_SYMBOL_GPL(qcom_unregister_ssr_notifier);
+
+static int ssr_notify_start(struct rproc_subdev *subdev)
+{
+ return 0;
+}
+
+static void ssr_notify_stop(struct rproc_subdev *subdev)
+{
+ struct qcom_rproc_ssr *ssr = to_ssr_subdev(subdev);
+
+ blocking_notifier_call_chain(&ssr_notifiers, 0, (void *)ssr->name);
+}
+
+/**
+ * qcom_add_ssr_subdev() - register subdevice as restart notification source
+ * @rproc: rproc handle
+ * @ssr: SSR subdevice handle
+ * @ssr_name: identifier to use for notifications originating from @rproc
+ *
+ * As the @ssr is registered with the @rproc SSR events will be sent to all
+ * registered listeners in the system as the remoteproc is shut down.
+ */
+void qcom_add_ssr_subdev(struct rproc *rproc, struct qcom_rproc_ssr *ssr,
+ const char *ssr_name)
+{
+ ssr->name = ssr_name;
+
+ rproc_add_subdev(rproc, &ssr->subdev, ssr_notify_start, ssr_notify_stop);
+}
+EXPORT_SYMBOL_GPL(qcom_add_ssr_subdev);
+
+/**
+ * qcom_remove_ssr_subdev() - remove subdevice as restart notification source
+ * @rproc: rproc handle
+ * @ssr: SSR subdevice handle
+ */
+void qcom_remove_ssr_subdev(struct rproc *rproc, struct qcom_rproc_ssr *ssr)
+{
+ rproc_remove_subdev(rproc, &ssr->subdev);
+}
+EXPORT_SYMBOL_GPL(qcom_remove_ssr_subdev);
+
MODULE_DESCRIPTION("Qualcomm Remoteproc helper driver");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/remoteproc/qcom_common.h b/drivers/remoteproc/qcom_common.h
index db5c826d5cd4..fab28b64b8ea 100644
--- a/drivers/remoteproc/qcom_common.h
+++ b/drivers/remoteproc/qcom_common.h
@@ -12,6 +12,12 @@ struct qcom_rproc_subdev {
struct qcom_smd_edge *edge;
};
+struct qcom_rproc_ssr {
+ struct rproc_subdev subdev;
+
+ const char *name;
+};
+
struct resource_table *qcom_mdt_find_rsc_table(struct rproc *rproc,
const struct firmware *fw,
int *tablesz);
@@ -19,4 +25,8 @@ struct resource_table *qcom_mdt_find_rsc_table(struct rproc *rproc,
void qcom_add_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd);
void qcom_remove_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd);
+void qcom_add_ssr_subdev(struct rproc *rproc, struct qcom_rproc_ssr *ssr,
+ const char *ssr_name);
+void qcom_remove_ssr_subdev(struct rproc *rproc, struct qcom_rproc_ssr *ssr);
+
#endif
diff --git a/drivers/remoteproc/qcom_q6v5_pil.c b/drivers/remoteproc/qcom_q6v5_pil.c
index 8fd697a3cf8f..2d3d5ac92c06 100644
--- a/drivers/remoteproc/qcom_q6v5_pil.c
+++ b/drivers/remoteproc/qcom_q6v5_pil.c
@@ -153,6 +153,7 @@ struct q6v5 {
size_t mpss_size;
struct qcom_rproc_subdev smd_subdev;
+ struct qcom_rproc_ssr ssr_subdev;
};
static int q6v5_regulator_init(struct device *dev, struct reg_info *regs,
@@ -867,7 +868,8 @@ static int q6v5_init_clocks(struct device *dev, struct clk **clks,
static int q6v5_init_reset(struct q6v5 *qproc)
{
- qproc->mss_restart = devm_reset_control_get(qproc->dev, NULL);
+ qproc->mss_restart = devm_reset_control_get_exclusive(qproc->dev,
+ NULL);
if (IS_ERR(qproc->mss_restart)) {
dev_err(qproc->dev, "failed to acquire mss restart\n");
return PTR_ERR(qproc->mss_restart);
@@ -1038,6 +1040,7 @@ static int q6v5_probe(struct platform_device *pdev)
}
qcom_add_smd_subdev(rproc, &qproc->smd_subdev);
+ qcom_add_ssr_subdev(rproc, &qproc->ssr_subdev, "mpss");
ret = rproc_add(rproc);
if (ret)
@@ -1058,6 +1061,7 @@ static int q6v5_remove(struct platform_device *pdev)
rproc_del(qproc->rproc);
qcom_remove_smd_subdev(qproc->rproc, &qproc->smd_subdev);
+ qcom_remove_ssr_subdev(qproc->rproc, &qproc->ssr_subdev);
rproc_free(qproc->rproc);
return 0;
diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c
index 564061dcc019..eab14b414bf0 100644
--- a/drivers/remoteproc/remoteproc_core.c
+++ b/drivers/remoteproc/remoteproc_core.c
@@ -794,7 +794,7 @@ static void rproc_remove_subdevices(struct rproc *rproc)
{
struct rproc_subdev *subdev;
- list_for_each_entry(subdev, &rproc->subdevs, node)
+ list_for_each_entry_reverse(subdev, &rproc->subdevs, node)
subdev->remove(subdev);
}
@@ -1119,7 +1119,7 @@ static void rproc_crash_handler_work(struct work_struct *work)
}
/**
- * __rproc_boot() - boot a remote processor
+ * rproc_boot() - boot a remote processor
* @rproc: handle of a remote processor
*
* Boot a remote processor (i.e. load its firmware, power it on, ...).
@@ -1129,7 +1129,7 @@ static void rproc_crash_handler_work(struct work_struct *work)
*
* Returns 0 on success, and an appropriate error value otherwise.
*/
-static int __rproc_boot(struct rproc *rproc)
+int rproc_boot(struct rproc *rproc)
{
const struct firmware *firmware_p;
struct device *dev;
@@ -1180,15 +1180,6 @@ unlock_mutex:
mutex_unlock(&rproc->lock);
return ret;
}
-
-/**
- * rproc_boot() - boot a remote processor
- * @rproc: handle of a remote processor
- */
-int rproc_boot(struct rproc *rproc)
-{
- return __rproc_boot(rproc);
-}
EXPORT_SYMBOL(rproc_boot);
/**
@@ -1369,7 +1360,7 @@ static void rproc_type_release(struct device *dev)
kfree(rproc);
}
-static struct device_type rproc_type = {
+static const struct device_type rproc_type = {
.name = "remoteproc",
.release = rproc_type_release,
};
@@ -1440,6 +1431,7 @@ struct rproc *rproc_alloc(struct device *dev, const char *name,
rproc->dev.parent = dev;
rproc->dev.type = &rproc_type;
rproc->dev.class = &rproc_class;
+ rproc->dev.driver_data = rproc;
/* Assign a unique device index and name */
rproc->index = ida_simple_get(&rproc_dev_index, 0, 0, GFP_KERNEL);
@@ -1579,6 +1571,23 @@ void rproc_remove_subdev(struct rproc *rproc, struct rproc_subdev *subdev)
EXPORT_SYMBOL(rproc_remove_subdev);
/**
+ * rproc_get_by_child() - acquire rproc handle of @dev's ancestor
+ * @dev: child device to find ancestor of
+ *
+ * Returns the ancestor rproc instance, or NULL if not found.
+ */
+struct rproc *rproc_get_by_child(struct device *dev)
+{
+ for (dev = dev->parent; dev; dev = dev->parent) {
+ if (dev->type == &rproc_type)
+ return dev->driver_data;
+ }
+
+ return NULL;
+}
+EXPORT_SYMBOL(rproc_get_by_child);
+
+/**
* rproc_report_crash() - rproc crash reporter function
* @rproc: remote processor
* @type: crash type
diff --git a/drivers/remoteproc/remoteproc_internal.h b/drivers/remoteproc/remoteproc_internal.h
index 1e9e5b3f021c..c1077bec5d0b 100644
--- a/drivers/remoteproc/remoteproc_internal.h
+++ b/drivers/remoteproc/remoteproc_internal.h
@@ -48,7 +48,6 @@ struct rproc_fw_ops {
/* from remoteproc_core.c */
void rproc_release(struct kref *kref);
irqreturn_t rproc_vq_interrupt(struct rproc *rproc, int vq_id);
-int rproc_boot_nowait(struct rproc *rproc);
void rproc_vdev_release(struct kref *ref);
/* from remoteproc_virtio.c */
diff --git a/drivers/remoteproc/st_remoteproc.c b/drivers/remoteproc/st_remoteproc.c
index d534bf23dc56..aacef0ea3b90 100644
--- a/drivers/remoteproc/st_remoteproc.c
+++ b/drivers/remoteproc/st_remoteproc.c
@@ -212,7 +212,8 @@ static int st_rproc_parse_dt(struct platform_device *pdev)
int err;
if (ddata->config->sw_reset) {
- ddata->sw_reset = devm_reset_control_get(dev, "sw_reset");
+ ddata->sw_reset = devm_reset_control_get_exclusive(dev,
+ "sw_reset");
if (IS_ERR(ddata->sw_reset)) {
dev_err(dev, "Failed to get S/W Reset\n");
return PTR_ERR(ddata->sw_reset);
@@ -220,7 +221,8 @@ static int st_rproc_parse_dt(struct platform_device *pdev)
}
if (ddata->config->pwr_reset) {
- ddata->pwr_reset = devm_reset_control_get(dev, "pwr_reset");
+ ddata->pwr_reset = devm_reset_control_get_exclusive(dev,
+ "pwr_reset");
if (IS_ERR(ddata->pwr_reset)) {
dev_err(dev, "Failed to get Power Reset\n");
return PTR_ERR(ddata->pwr_reset);
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 9fca977ef18d..d0fc331972d2 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -1,6 +1,15 @@
#
# QCOM Soc drivers
#
+config QCOM_GLINK_SSR
+ tristate "Qualcomm Glink SSR driver"
+ depends on RPMSG
+ depends on QCOM_RPROC_COMMON
+ help
+ Say y here to enable GLINK SSR support. The GLINK SSR driver
+ implements the SSR protocol for notifying the remote processor about
+ neighboring subsystems going up or down.
+
config QCOM_GSBI
tristate "QCOM General Serial Bus Interface"
depends on ARCH_QCOM
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index 414f0de274fa..f151de41eb93 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -1,3 +1,4 @@
+obj-$(CONFIG_QCOM_GLINK_SSR) += glink_ssr.o
obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o
obj-$(CONFIG_QCOM_MDT_LOADER) += mdt_loader.o
obj-$(CONFIG_QCOM_PM) += spm.o
diff --git a/drivers/soc/qcom/glink_ssr.c b/drivers/soc/qcom/glink_ssr.c
new file mode 100644
index 000000000000..19c7399eddb5
--- /dev/null
+++ b/drivers/soc/qcom/glink_ssr.c
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2014-2017, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017, Linaro Ltd.
+ *
+ * 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/completion.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/rpmsg.h>
+#include <linux/remoteproc/qcom_rproc.h>
+
+/**
+ * struct do_cleanup_msg - The data structure for an SSR do_cleanup message
+ * version: The G-Link SSR protocol version
+ * command: The G-Link SSR command - do_cleanup
+ * seq_num: Sequence number
+ * name_len: Length of the name of the subsystem being restarted
+ * name: G-Link edge name of the subsystem being restarted
+ */
+struct do_cleanup_msg {
+ __le32 version;
+ __le32 command;
+ __le32 seq_num;
+ __le32 name_len;
+ char name[32];
+};
+
+/**
+ * struct cleanup_done_msg - The data structure for an SSR cleanup_done message
+ * version: The G-Link SSR protocol version
+ * response: The G-Link SSR response to a do_cleanup command, cleanup_done
+ * seq_num: Sequence number
+ */
+struct cleanup_done_msg {
+ __le32 version;
+ __le32 response;
+ __le32 seq_num;
+};
+
+/**
+ * G-Link SSR protocol commands
+ */
+#define GLINK_SSR_DO_CLEANUP 0
+#define GLINK_SSR_CLEANUP_DONE 1
+
+struct glink_ssr {
+ struct device *dev;
+ struct rpmsg_endpoint *ept;
+
+ struct notifier_block nb;
+
+ u32 seq_num;
+ struct completion completion;
+};
+
+static int qcom_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)) {
+ dev_err(ssr->dev, "message too short\n");
+ return -EINVAL;
+ }
+
+ if (le32_to_cpu(msg->version) != 0)
+ return -EINVAL;
+
+ if (le32_to_cpu(msg->response) != GLINK_SSR_CLEANUP_DONE)
+ return 0;
+
+ if (le32_to_cpu(msg->seq_num) != ssr->seq_num) {
+ dev_err(ssr->dev, "invalid sequence number of response\n");
+ return -EINVAL;
+ }
+
+ complete(&ssr->completion);
+
+ return 0;
+}
+
+static int qcom_glink_ssr_notify(struct notifier_block *nb, unsigned long event,
+ void *data)
+{
+ struct glink_ssr *ssr = container_of(nb, struct glink_ssr, nb);
+ struct do_cleanup_msg msg;
+ char *ssr_name = data;
+ int ret;
+
+ 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(ssr_name));
+ strlcpy(msg.name, ssr_name, sizeof(msg.name));
+
+ ret = rpmsg_send(ssr->ept, &msg, sizeof(msg));
+ if (ret < 0)
+ dev_err(ssr->dev, "failed to send cleanup message\n");
+
+ ret = wait_for_completion_timeout(&ssr->completion, HZ);
+ if (!ret)
+ dev_err(ssr->dev, "timeout waiting for cleanup done message\n");
+
+ return NOTIFY_DONE;
+}
+
+static int qcom_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_completion(&ssr->completion);
+
+ ssr->dev = &rpdev->dev;
+ ssr->ept = rpdev->ept;
+ ssr->nb.notifier_call = qcom_glink_ssr_notify;
+
+ dev_set_drvdata(&rpdev->dev, ssr);
+
+ return qcom_register_ssr_notifier(&ssr->nb);
+}
+
+static void qcom_glink_ssr_remove(struct rpmsg_device *rpdev)
+{
+ struct glink_ssr *ssr = dev_get_drvdata(&rpdev->dev);
+
+ qcom_unregister_ssr_notifier(&ssr->nb);
+}
+
+static const struct rpmsg_device_id qcom_glink_ssr_match[] = {
+ { "glink_ssr" },
+ {}
+};
+
+static struct rpmsg_driver qcom_glink_ssr_driver = {
+ .probe = qcom_glink_ssr_probe,
+ .remove = qcom_glink_ssr_remove,
+ .callback = qcom_glink_ssr_callback,
+ .id_table = qcom_glink_ssr_match,
+ .drv = {
+ .name = "qcom_glink_ssr",
+ },
+};
+module_rpmsg_driver(qcom_glink_ssr_driver);
+
+MODULE_ALIAS("rpmsg:glink_ssr");
+MODULE_DESCRIPTION("Qualcomm GLINK SSR notifier");
+MODULE_LICENSE("GPL v2");