diff options
Diffstat (limited to 'arch/arm/mach-imx/mu.c')
-rw-r--r-- | arch/arm/mach-imx/mu.c | 124 |
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; |