summaryrefslogtreecommitdiff
path: root/arch/arm/mach-imx/mu.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-imx/mu.c')
-rw-r--r--arch/arm/mach-imx/mu.c124
1 files changed, 117 insertions, 7 deletions
diff --git a/arch/arm/mach-imx/mu.c b/arch/arm/mach-imx/mu.c
index ebeff5dec036..dc5046530bc3 100644
--- a/arch/arm/mach-imx/mu.c
+++ b/arch/arm/mach-imx/mu.c
@@ -19,6 +19,7 @@
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
+#include <linux/notifier.h>
#include <linux/platform_device.h>
#include "common.h"
#include "hardware.h"
@@ -29,10 +30,12 @@
#define MU_ATR0_OFFSET 0x0
#define MU_ARR0_OFFSET 0x10
+#define MU_ARR1_OFFSET 0x14
#define MU_ASR 0x20
#define MU_ACR 0x24
#define MU_LPM_HANDSHAKE_INDEX 0
+#define MU_RPMSG_HANDSHAKE_INDEX 1
#define MU_LPM_BUS_HIGH_READY_FOR_M4 0xFFFF6666
#define MU_LPM_M4_FREQ_CHANGE_READY 0xFFFF7777
#define MU_LPM_M4_REQUEST_HIGH_BUS 0x2222CCCC
@@ -44,10 +47,19 @@
#define MU_LPM_M4_WAKEUP_ENABLE_MASK 0xF
#define MU_LPM_M4_WAKEUP_ENABLE_SHIFT 0x0
+struct imx_mu_rpmsg_box {
+ const char *name;
+ struct blocking_notifier_head notifier;
+};
+
+static struct imx_mu_rpmsg_box mu_rpmsg_box = {
+ .name = "m4",
+};
+
static void __iomem *mu_base;
static u32 mu_int_en;
static u32 m4_message;
-static struct delayed_work mu_work;
+static struct delayed_work mu_work, rpmsg_work;
static u32 m4_wake_irqs[4];
static bool m4_freq_low;
@@ -86,21 +98,61 @@ static irqreturn_t mcc_m4_dummy_isr(int irq, void *param)
return IRQ_HANDLED;
}
-static void imx_mu_send_message(unsigned int index, unsigned int data)
+static int imx_mu_send_message(unsigned int index, unsigned int data)
{
- u32 val;
+ u32 val, ep;
+ int i, te_flag = 0;
unsigned long timeout = jiffies + msecs_to_jiffies(500);
- /* wait for transfer buffer empty */
+ /* wait for transfer buffer empty, and no event pending */
do {
val = readl_relaxed(mu_base + MU_ASR);
+ ep = val & BIT(4);
if (time_after(jiffies, timeout)) {
pr_err("Waiting MU transmit buffer empty timeout!\n");
- break;
+ return -EIO;
}
- } while ((val & (1 << (20 + index))) == 0);
+ } while (((val & (1 << (20 + 3 - index))) == 0) || (ep == BIT(4)));
writel_relaxed(data, mu_base + index * 0x4 + MU_ATR0_OFFSET);
+
+ /*
+ * make a double check, and make sure that TEn is not
+ * empty after write
+ */
+ val = readl_relaxed(mu_base + MU_ASR);
+ ep = val & BIT(4);
+ if (((val & (1 << (20 + (3 - index)))) == 0) || (ep == BIT(4)))
+ return 0;
+ else
+ te_flag = 1;
+
+ /*
+ * suspect that there is bug here. TEn flag is not
+ * changed immediately, after the ATRn is filled up.
+ *
+ */
+ for (i = 0; i < 100; i++) {
+ val = readl_relaxed(mu_base + MU_ASR);
+ ep = val & BIT(4);
+ if (((val & (1 << (20 + 3 - index))) == 0) || (ep == BIT(4))) {
+ /*
+ * IC BUG here. TEn flag is changes, after the
+ * ATRn is filled with MSG for a while.
+ */
+ te_flag = 0;
+ break;
+ } else if (time_after(jiffies, timeout)) {
+ /* Can't see TEn 1->0, maybe already handled! */
+ te_flag = 1;
+ break;
+ }
+ }
+ if (te_flag == 0)
+ pr_info("BUG: TEn is not changed immediately"
+ "when ATRn is filled up.\n");
+
+ return 0;
}
static void mu_work_handler(struct work_struct *work)
@@ -158,6 +210,48 @@ static void mu_work_handler(struct work_struct *work)
mu_base + MU_ACR);
}
+int imx_mu_rpmsg_send(unsigned int rpmsg)
+{
+ return imx_mu_send_message(MU_RPMSG_HANDSHAKE_INDEX, rpmsg);
+}
+
+int imx_mu_rpmsg_register_nb(const char *name, struct notifier_block *nb)
+{
+ if ((name == NULL) || (nb == NULL))
+ return -EINVAL;
+
+ if (!strcmp(mu_rpmsg_box.name, name))
+ blocking_notifier_chain_register(&(mu_rpmsg_box.notifier), nb);
+ else
+ return -ENOENT;
+
+ return 0;
+}
+
+int imx_mu_rpmsg_unregister_nb(const char *name, struct notifier_block *nb)
+{
+ if ((name == NULL) || (nb == NULL))
+ return -EINVAL;
+
+ if (!strcmp(mu_rpmsg_box.name, name))
+ blocking_notifier_chain_unregister(&(mu_rpmsg_box.notifier),
+ nb);
+ else
+ return -ENOENT;
+
+ return 0;
+}
+
+static void rpmsg_work_handler(struct work_struct *work)
+{
+
+ blocking_notifier_call_chain(&(mu_rpmsg_box.notifier), 4,
+ (void *)m4_message);
+ m4_message = 0;
+ writel_relaxed(readl_relaxed(mu_base + MU_ACR) | BIT(26),
+ mu_base + MU_ACR);
+}
+
/*!
* \brief This function clears the CPU-to-CPU int flag for the particular core.
*
@@ -354,6 +448,15 @@ static irqreturn_t imx_mu_isr(int irq, void *param)
schedule_delayed_work(&mu_work, 0);
}
+ /* RPMSG */
+ if (irqs & (1 << 26)) {
+ /* get message from receive buffer */
+ m4_message = readl_relaxed(mu_base + MU_ARR1_OFFSET);
+ writel_relaxed(readl_relaxed(mu_base + MU_ACR) & (~BIT(26)),
+ mu_base + MU_ACR);
+ schedule_delayed_work(&rpmsg_work, 0);
+ }
+
/*
* MCC CPU-to-CPU interrupt.
* Each core can interrupt the other. There are two logical signals:
@@ -434,7 +537,7 @@ static int imx_mu_probe(struct platform_device *pdev)
irq = platform_get_irq(pdev, 0);
ret = request_irq(irq, imx_mu_isr,
- IRQF_EARLY_RESUME, "imx-mu", NULL);
+ IRQF_EARLY_RESUME, "imx-mu", &mu_rpmsg_box);
if (ret) {
pr_err("%s: register interrupt %d failed, rc %d\n",
__func__, irq, ret);
@@ -461,6 +564,11 @@ static int imx_mu_probe(struct platform_device *pdev)
/* enable the bit31(GIE3) of MU_ACR, used for MCC */
writel_relaxed(readl_relaxed(mu_base + MU_ACR) | BIT(31),
mu_base + MU_ACR);
+
+ INIT_DELAYED_WORK(&rpmsg_work, rpmsg_work_handler);
+ /* enable the bit26(RIE1) of MU_ACR */
+ writel_relaxed(readl_relaxed(mu_base + MU_ACR) | BIT(26),
+ mu_base + MU_ACR);
} else {
INIT_DELAYED_WORK(&mu_work, mu_work_handler);
@@ -475,6 +583,8 @@ static int imx_mu_probe(struct platform_device *pdev)
imx_gpc_add_m4_wake_up_irq(irq, true);
}
+ BLOCKING_INIT_NOTIFIER_HEAD(&(mu_rpmsg_box.notifier));
+
pr_info("MU is ready for cross core communication!\n");
return 0;