diff options
author | Anson Huang <b20788@freescale.com> | 2014-12-04 10:02:15 +0800 |
---|---|---|
committer | Nitin Garg <nitin.garg@freescale.com> | 2015-09-17 09:20:19 -0500 |
commit | 5d26205087e6822ff1e37181e079219ab221d3df (patch) | |
tree | f8d1f249998a35708b6becd447c4a02b5acdb1a0 /arch/arm | |
parent | d0c7919311134be3df14e4de121c0c9997648eeb (diff) |
MLK-9955-8 arm: imx: add mu driver support
add MU driver support in mach-imx, all the MU functions
and communications between A9 and M4 will be done in
this file, including MCC, shared clk/power management.
Signed-off-by: Anson Huang <b20788@freescale.com>
Diffstat (limited to 'arch/arm')
-rw-r--r-- | arch/arm/mach-imx/Makefile | 2 | ||||
-rw-r--r-- | arch/arm/mach-imx/mu.c | 216 |
2 files changed, 217 insertions, 1 deletions
diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile index 8015762df4e0..d3db2ba47e50 100644 --- a/arch/arm/mach-imx/Makefile +++ b/arch/arm/mach-imx/Makefile @@ -103,7 +103,7 @@ obj-$(CONFIG_SMP) += headsmp.o platsmp.o obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o obj-$(CONFIG_SOC_IMX6Q) += clk-imx6q.o mach-imx6q.o obj-$(CONFIG_SOC_IMX6SL) += clk-imx6sl.o mach-imx6sl.o -obj-$(CONFIG_SOC_IMX6SX) += clk-imx6sx.o mach-imx6sx.o +obj-$(CONFIG_SOC_IMX6SX) += clk-imx6sx.o mach-imx6sx.o mu.o ifeq ($(CONFIG_SUSPEND),y) AFLAGS_suspend-imx6.o :=-Wa,-march=armv7-a diff --git a/arch/arm/mach-imx/mu.c b/arch/arm/mach-imx/mu.c new file mode 100644 index 000000000000..c312ce403d4e --- /dev/null +++ b/arch/arm/mach-imx/mu.c @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2014 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/busfreq-imx6.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include "common.h" +#include "hardware.h" + +#define MU_ATR0_OFFSET 0x0 +#define MU_ARR0_OFFSET 0x10 +#define MU_ASR 0x20 +#define MU_ACR 0x24 + +#define MU_LPM_HANDSHAKE_INDEX 0 +#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 +#define MU_LPM_M4_RELEASE_HIGH_BUS 0x2222BBBB +#define MU_LPM_M4_WAKEUP_SRC_VAL 0x55555000 +#define MU_LPM_M4_WAKEUP_SRC_MASK 0xFFFFF000 +#define MU_LPM_M4_WAKEUP_IRQ_MASK 0xFF0 +#define MU_LPM_M4_WAKEUP_IRQ_SHIFT 0x4 +#define MU_LPM_M4_WAKEUP_ENABLE_MASK 0xF +#define MU_LPM_M4_WAKEUP_ENABLE_SHIFT 0x0 + +static void __iomem *mu_base; +static u32 m4_message; +static struct delayed_work mu_work; +static u32 m4_wake_irqs[4]; +static bool m4_freq_low; + +bool imx_mu_is_m4_in_low_freq(void) +{ + return m4_freq_low; +} + +void imx_mu_enable_m4_irqs_in_gic(bool enable) +{ + int i, j; + + for (i = 0; i < 4; i++) { + if (m4_wake_irqs[i] == 0) + continue; + for (j = 0; j < 32; j++) { + if (m4_wake_irqs[i] & (1 << j)) { + if (enable) + enable_irq((i + 1) * 32 + j); + else + disable_irq((i + 1) * 32 + j); + } + } + } +} + +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) +{ + u32 val; + unsigned long timeout = jiffies + msecs_to_jiffies(500); + + /* wait for transfer buffer empty */ + do { + val = readl_relaxed(mu_base + MU_ASR); + if (time_after(jiffies, timeout)) { + pr_err("Waiting MU transmit buffer empty timeout!\n"); + break; + } + } while ((val & (1 << (20 + index))) == 0); + + writel_relaxed(data, mu_base + index * 0x4 + MU_ATR0_OFFSET); +} + +static void mu_work_handler(struct work_struct *work) +{ + int ret; + u32 irq, enable, idx, mask; + + pr_debug("receive M4 message 0x%x\n", m4_message); + + switch (m4_message) { + case MU_LPM_M4_REQUEST_HIGH_BUS: + request_bus_freq(BUS_FREQ_HIGH); + imx6sx_set_m4_highfreq(true); + imx_mu_send_message(MU_LPM_HANDSHAKE_INDEX, + MU_LPM_BUS_HIGH_READY_FOR_M4); + m4_freq_low = false; + break; + case MU_LPM_M4_RELEASE_HIGH_BUS: + release_bus_freq(BUS_FREQ_HIGH); + imx6sx_set_m4_highfreq(false); + imx_mu_send_message(MU_LPM_HANDSHAKE_INDEX, + MU_LPM_M4_FREQ_CHANGE_READY); + m4_freq_low = true; + break; + default: + if ((m4_message & MU_LPM_M4_WAKEUP_SRC_MASK) == + MU_LPM_M4_WAKEUP_SRC_VAL) { + irq = (m4_message & MU_LPM_M4_WAKEUP_IRQ_MASK) >> + MU_LPM_M4_WAKEUP_IRQ_SHIFT; + + enable = (m4_message & MU_LPM_M4_WAKEUP_ENABLE_MASK) >> + MU_LPM_M4_WAKEUP_ENABLE_SHIFT; + + idx = irq / 32 - 1; + mask = 1 << irq % 32; + + if (enable && can_request_irq(irq, 0)) { + ret = request_irq(irq, mcc_m4_dummy_isr, + IRQF_NO_SUSPEND, "imx-m4-dummy", NULL); + if (ret) { + pr_err("%s: register interrupt %d failed, rc %d\n", + __func__, irq, ret); + break; + } + disable_irq(irq); + m4_wake_irqs[idx] = m4_wake_irqs[idx] | mask; + } + imx_gpc_add_m4_wake_up_irq(irq, enable); + } + break; + } + m4_message = 0; + /* enable RIE3 interrupt */ + writel_relaxed(readl_relaxed(mu_base + MU_ACR) | BIT(27), + mu_base + MU_ACR); +} + +static irqreturn_t imx_mu_isr(int irq, void *param) +{ + u32 irqs; + + irqs = readl_relaxed(mu_base + MU_ASR); + + if (irqs & (1 << 27)) { + /* get message from receive buffer */ + m4_message = readl_relaxed(mu_base + MU_ARR0_OFFSET); + /* disable RIE3 interrupt */ + writel_relaxed(readl_relaxed(mu_base + MU_ACR) & (~BIT(27)), + mu_base + MU_ACR); + schedule_delayed_work(&mu_work, 0); + } + + return IRQ_HANDLED; +} + +static int imx_mu_probe(struct platform_device *pdev) +{ + int ret; + u32 irq; + struct device_node *np; + + np = of_find_compatible_node(NULL, NULL, "fsl,imx6sx-mu"); + mu_base = of_iomap(np, 0); + WARN_ON(!mu_base); + + irq = platform_get_irq(pdev, 0); + + ret = request_irq(irq, imx_mu_isr, + IRQF_EARLY_RESUME, "imx-mu", NULL); + if (ret) { + pr_err("%s: register interrupt %d failed, rc %d\n", + __func__, irq, ret); + return ret; + } + INIT_DELAYED_WORK(&mu_work, mu_work_handler); + + /* enable the bit27(RIE3) of MU_ACR */ + writel_relaxed(readl_relaxed(mu_base + MU_ACR) | BIT(27), + mu_base + MU_ACR); + + /* MU always as a wakeup source for low power mode */ + imx_gpc_add_m4_wake_up_irq(irq, true); + + pr_info("MU is ready for cross core communication!\n"); + + return 0; +} + +static const struct of_device_id imx_mu_ids[] = { + { .compatible = "fsl,imx6sx-mu" }, + { } +}; + +static struct platform_driver imx_mu_driver = { + .driver = { + .name = "imx-mu", + .owner = THIS_MODULE, + .of_match_table = imx_mu_ids, + }, + .probe = imx_mu_probe, +}; + +static int __init imx6_mu_init(void) +{ + return platform_driver_register(&imx_mu_driver); +} +subsys_initcall(imx6_mu_init); |