| /* |
| * Copyright (C) 2016 Freescale Semiconductor, Inc. |
| * Copyright 2017-2018 NXP |
| * |
| * SPDX-License-Identifier: GPL-2.0+ |
| */ |
| |
| /* Includes */ |
| #include <linux/arm-smccc.h> |
| #include <linux/completion.h> |
| #include <linux/err.h> |
| #include <linux/kernel.h> |
| #include <linux/of.h> |
| #include <linux/of_irq.h> |
| #include <linux/of_address.h> |
| #include <linux/of_device.h> |
| #include <linux/of_fdt.h> |
| #include <linux/interrupt.h> |
| #include <linux/irq.h> |
| #include <linux/mx8_mu.h> |
| #include <linux/syscore_ops.h> |
| #include <linux/suspend.h> |
| |
| #include <soc/imx/fsl_hvc.h> |
| #include <soc/imx8/sc/svc/irq/api.h> |
| #include <soc/imx8/sc/ipc.h> |
| #include <soc/imx8/sc/sci.h> |
| |
| #include "rpc.h" |
| |
| /* Local Defines */ |
| #define MU_SIZE 0x10000 |
| |
| /* Local Types */ |
| unsigned int scu_mu_id; |
| static void __iomem *mu_base_virtaddr; |
| static struct delayed_work scu_mu_work; |
| static sc_ipc_t mu_ipcHandle; |
| |
| /* Local functions */ |
| |
| /* Local variables */ |
| static uint32_t gIPCport; |
| static sc_rpc_msg_t *rx_msg; |
| static bool scu_mu_init; |
| struct completion rx_completion; |
| |
| DEFINE_MUTEX(scu_mu_mutex); |
| |
| static BLOCKING_NOTIFIER_HEAD(SCU_notifier_chain); |
| |
| EXPORT_SYMBOL(sc_pm_set_resource_power_mode); |
| EXPORT_SYMBOL(sc_pm_get_resource_power_mode); |
| EXPORT_SYMBOL(sc_pm_cpu_start); |
| EXPORT_SYMBOL(sc_misc_set_control); |
| EXPORT_SYMBOL(sc_pm_clock_enable); |
| EXPORT_SYMBOL(sc_pm_set_clock_rate); |
| /*--------------------------------------------------------------------------*/ |
| /* RPC command/response */ |
| /*--------------------------------------------------------------------------*/ |
| void sc_call_rpc(sc_ipc_t handle, sc_rpc_msg_t *msg, sc_bool_t no_resp) |
| { |
| struct arm_smccc_res res; |
| unsigned long timeout; |
| |
| if (in_interrupt()) { |
| pr_warn("Cannot make SC IPC calls from an interrupt context\n"); |
| dump_stack(); |
| return; |
| } |
| mutex_lock(&scu_mu_mutex); |
| |
| reinit_completion(&rx_completion); |
| rx_msg = msg; |
| if (xen_initial_domain()) { |
| arm_smccc_hvc(FSL_HVC_SC, (uint64_t)msg, no_resp, 0, 0, 0, 0, |
| 0, &res); |
| if (res.a0) |
| printk("Error FSL_HVC_SC %ld\n", res.a0); |
| } else { |
| sc_ipc_write(handle, msg); |
| if (!no_resp) { |
| timeout = wait_for_completion_timeout(&rx_completion, HZ / 10); |
| if (!timeout) { |
| pr_err("Timeout for IPC response!\n"); |
| mutex_unlock(&scu_mu_mutex); |
| return; |
| } |
| } |
| } |
| |
| mutex_unlock(&scu_mu_mutex); |
| } |
| EXPORT_SYMBOL(sc_call_rpc); |
| |
| /*--------------------------------------------------------------------------*/ |
| /* Get MU base address for specified IPC channel */ |
| /*--------------------------------------------------------------------------*/ |
| static uint32_t *sc_ipc_get_mu_base(uint32_t id) |
| { |
| uint32_t *base; |
| |
| /* Check parameters */ |
| if (id >= SC_NUM_IPC) |
| base = NULL; |
| else |
| base = (uint32_t *) (mu_base_virtaddr + (id * MU_SIZE)); |
| |
| return base; |
| } |
| |
| /*--------------------------------------------------------------------------*/ |
| /* Get the MU ID used by Linux */ |
| /*--------------------------------------------------------------------------*/ |
| int sc_ipc_getMuID(uint32_t *mu_id) |
| { |
| if (scu_mu_init) { |
| *mu_id = scu_mu_id; |
| return SC_ERR_NONE; |
| } |
| return SC_ERR_UNAVAILABLE; |
| } |
| |
| EXPORT_SYMBOL(sc_ipc_getMuID); |
| |
| /*--------------------------------------------------------------------------*/ |
| /* Open an IPC channel */ |
| /*--------------------------------------------------------------------------*/ |
| sc_err_t sc_ipc_requestInt(sc_ipc_t *handle, uint32_t id) |
| { |
| return SC_ERR_NONE; |
| } |
| |
| /*--------------------------------------------------------------------------*/ |
| /* Open an IPC channel */ |
| /*--------------------------------------------------------------------------*/ |
| sc_err_t sc_ipc_open(sc_ipc_t *handle, uint32_t id) |
| { |
| uint32_t *base; |
| |
| mutex_lock(&scu_mu_mutex); |
| |
| if (!scu_mu_init) { |
| mutex_unlock(&scu_mu_mutex); |
| return SC_ERR_UNAVAILABLE; |
| } |
| /* Get MU base associated with IPC channel */ |
| base = sc_ipc_get_mu_base(id); |
| |
| if (base == NULL) { |
| mutex_unlock(&scu_mu_mutex); |
| return SC_ERR_IPC; |
| } |
| |
| *handle = (sc_ipc_t) task_pid_vnr(current); |
| |
| mutex_unlock(&scu_mu_mutex); |
| |
| return SC_ERR_NONE; |
| } |
| EXPORT_SYMBOL(sc_ipc_open); |
| /*--------------------------------------------------------------------------*/ |
| /* Close an IPC channel */ |
| /*--------------------------------------------------------------------------*/ |
| void sc_ipc_close(sc_ipc_t handle) |
| { |
| uint32_t *base; |
| |
| mutex_lock(&scu_mu_mutex); |
| |
| if (!scu_mu_init) { |
| mutex_unlock(&scu_mu_mutex); |
| return; |
| } |
| |
| /* Get MU base associated with IPC channel */ |
| base = sc_ipc_get_mu_base(gIPCport); |
| |
| /* TBD ***** What needs to be done here? */ |
| mutex_unlock(&scu_mu_mutex); |
| } |
| EXPORT_SYMBOL(sc_ipc_close); |
| |
| /*! |
| * This function reads a message from an IPC channel. |
| * |
| * @param[in] ipc id of channel read from |
| * @param[out] data pointer to message buffer to read |
| * |
| * This function will block if no message is available to be read. |
| */ |
| void sc_ipc_read(sc_ipc_t handle, void *data) |
| { |
| uint32_t *base; |
| uint8_t count = 0; |
| sc_rpc_msg_t *msg = (sc_rpc_msg_t *) data; |
| |
| /* Get MU base associated with IPC channel */ |
| base = sc_ipc_get_mu_base(gIPCport); |
| |
| if ((base == NULL) || (msg == NULL)) |
| return; |
| |
| /* Read first word */ |
| MU_ReceiveMsg(base, 0, (uint32_t *) msg); |
| count++; |
| |
| /* Check size */ |
| if (msg->size > SC_RPC_MAX_MSG) { |
| *((uint32_t *) msg) = 0; |
| return; |
| } |
| |
| /* Read remaining words */ |
| while (count < msg->size) { |
| MU_ReceiveMsg(base, count % MU_RR_COUNT, |
| &(msg->DATA.u32[count - 1])); |
| count++; |
| } |
| } |
| |
| /*! |
| * This function writes a message to an IPC channel. |
| * |
| * @param[in] ipc id of channel to write to |
| * @param[in] data pointer to message buffer to write |
| * |
| * This function will block if the outgoing buffer is full. |
| */ |
| void sc_ipc_write(sc_ipc_t handle, const void *data) |
| { |
| uint32_t *base; |
| uint8_t count = 0; |
| sc_rpc_msg_t *msg = (sc_rpc_msg_t *) data; |
| |
| /* Get MU base associated with IPC channel */ |
| base = sc_ipc_get_mu_base(gIPCport); |
| |
| if ((base == NULL) || (msg == NULL)) |
| return; |
| |
| /* Check size */ |
| if (msg->size > SC_RPC_MAX_MSG) |
| return; |
| |
| /* Write first word */ |
| MU_SendMessage(base, 0, *((uint32_t *) msg)); |
| count++; |
| |
| /* Write remaining words */ |
| while (count < msg->size) { |
| MU_SendMessage(base, count % MU_TR_COUNT, msg->DATA.u32[count - 1]); |
| count++; |
| } |
| } |
| |
| int register_scu_notifier(struct notifier_block *nb) |
| { |
| return blocking_notifier_chain_register(&SCU_notifier_chain, nb); |
| } |
| |
| EXPORT_SYMBOL(register_scu_notifier); |
| |
| int unregister_scu_notifier(struct notifier_block *nb) |
| { |
| return blocking_notifier_chain_unregister(&SCU_notifier_chain, nb); |
| } |
| |
| EXPORT_SYMBOL(unregister_scu_notifier); |
| |
| static int SCU_notifier_call_chain(unsigned long status, sc_irq_group_t *group) |
| { |
| return blocking_notifier_call_chain(&SCU_notifier_chain, status, |
| (void *)group); |
| } |
| |
| static void scu_mu_work_handler(struct work_struct *work) |
| { |
| uint32_t irq_status; |
| sc_err_t sciErr; |
| sc_irq_group_t i; |
| |
| /* Walk all groups interrupt callback, callback will judge if it's |
| * the right group for itself, return directly if not. |
| */ |
| for (i = 0; i < SC_IRQ_NUM_GROUP; i++) { |
| sciErr = sc_irq_status(mu_ipcHandle, SC_R_MU_1A, i, |
| &irq_status); |
| /* no irq? */ |
| if (!irq_status) |
| continue; |
| |
| SCU_notifier_call_chain(irq_status, &i); |
| } |
| } |
| |
| static irqreturn_t imx8_scu_mu_isr(int irq, void *param) |
| { |
| u32 irqs; |
| |
| irqs = (readl_relaxed(mu_base_virtaddr + 0x20) & (0xf << 24)); |
| if (irqs) { |
| sc_ipc_read(mu_ipcHandle, rx_msg); |
| complete(&rx_completion); |
| } |
| |
| irqs = (readl_relaxed(mu_base_virtaddr + 0x20) & (0xf << 28)); |
| if (irqs) { |
| /* Clear the General Interrupt */ |
| writel_relaxed(irqs, mu_base_virtaddr + 0x20); |
| /* Setup a bottom-half to handle the irq work. */ |
| schedule_delayed_work(&scu_mu_work, 0); |
| pm_system_wakeup(); |
| } |
| return IRQ_HANDLED; |
| } |
| |
| static void imx8_mu_resume(void) |
| { |
| int i; |
| |
| MU_Init(mu_base_virtaddr); |
| MU_EnableRxFullInt(mu_base_virtaddr, 0); |
| for (i = 0; i < MU_RR_COUNT; i++) |
| MU_EnableGeneralInt(mu_base_virtaddr, i); |
| } |
| |
| struct syscore_ops imx8_mu_syscore_ops = { |
| .resume = imx8_mu_resume, |
| }; |
| |
| /*Initialization of the MU code. */ |
| int __init imx8_mu_init(void) |
| { |
| struct device_node *np; |
| int irq; |
| int err; |
| sc_err_t sciErr; |
| |
| /* |
| * Get the address of MU to be used for communication with the SCU |
| */ |
| np = of_find_compatible_node(NULL, NULL, "fsl,imx8-mu"); |
| if (!np) { |
| pr_info("Cannot find MU entry in device tree\n"); |
| return 0; |
| } |
| mu_base_virtaddr = of_iomap(np, 0); |
| WARN_ON(!mu_base_virtaddr); |
| |
| err = of_property_read_u32_index(np, "fsl,scu_ap_mu_id", 0, &scu_mu_id); |
| if (err) |
| pr_info("imx8_mu_init: Cannot get mu_id err = %d\n", err); |
| |
| irq = of_irq_get(np, 0); |
| |
| if (irq <= 0) { |
| /* SCU works just fine without irq */ |
| pr_warn("imx8_mu_init: no irq: %d\n", irq); |
| } else { |
| err = request_irq(irq, imx8_scu_mu_isr, |
| IRQF_NO_SUSPEND, "imx8_mu_isr", NULL); |
| if (err) { |
| pr_err("imx8_mu_init: request_irq %d failed: %d\n", |
| irq, err); |
| return err; |
| } |
| |
| irq_set_irq_wake(irq, 1); |
| } |
| |
| if (!scu_mu_init) { |
| uint32_t i; |
| |
| INIT_DELAYED_WORK(&scu_mu_work, scu_mu_work_handler); |
| |
| /* Init MU */ |
| MU_Init(mu_base_virtaddr); |
| MU_EnableRxFullInt(mu_base_virtaddr, 0); |
| |
| #if 1 |
| /* Enable all RX interrupts */ |
| for (i = 0; i < MU_RR_COUNT; i++) |
| MU_EnableGeneralInt(mu_base_virtaddr, i); |
| #endif |
| gIPCport = scu_mu_id; |
| scu_mu_init = true; |
| init_completion(&rx_completion); |
| } |
| |
| sciErr = sc_ipc_open(&mu_ipcHandle, scu_mu_id); |
| if (sciErr != SC_ERR_NONE) { |
| pr_info("Cannot open MU channel to SCU\n"); |
| return sciErr; |
| }; |
| |
| /* Request for the high temp interrupt. */ |
| sciErr = sc_irq_enable(mu_ipcHandle, SC_R_MU_1A, SC_IRQ_GROUP_TEMP, |
| SC_IRQ_TEMP_PMIC0_HIGH, true); |
| |
| if (sciErr) |
| pr_info("Cannot request PMIC0_TEMP interrupt\n"); |
| |
| /* Request for the high temp interrupt. */ |
| sciErr = sc_irq_enable(mu_ipcHandle, SC_R_MU_1A, SC_IRQ_GROUP_TEMP, |
| SC_IRQ_TEMP_PMIC1_HIGH, true); |
| |
| if (sciErr) |
| pr_info("Cannot request PMIC1_TEMP interrupt\n"); |
| |
| /* Request for the rtc alarm interrupt. */ |
| sciErr = sc_irq_enable(mu_ipcHandle, SC_R_MU_1A, SC_IRQ_GROUP_RTC, |
| SC_IRQ_RTC, true); |
| |
| if (sciErr) |
| pr_info("Cannot request ALARM_RTC interrupt\n"); |
| |
| /* Request for the ON/OFF interrupt. */ |
| sciErr = sc_irq_enable(mu_ipcHandle, SC_R_MU_1A, SC_IRQ_GROUP_WAKE, |
| SC_IRQ_BUTTON, true); |
| |
| if (sciErr) |
| pr_info("Cannot request ON/OFF interrupt\n"); |
| |
| /* Request for the watchdog interrupt. */ |
| sciErr = sc_irq_enable(mu_ipcHandle, SC_R_MU_1A, SC_IRQ_GROUP_WDOG, |
| SC_IRQ_WDOG, true); |
| |
| if (sciErr) |
| pr_info("Cannot request WDOG interrupt\n"); |
| |
| sciErr = sc_irq_enable(mu_ipcHandle, SC_R_MU_1A, SC_IRQ_GROUP_WAKE, |
| SC_IRQ_PAD, true); |
| if (sciErr) |
| pr_info("Cannot request PAD interrupt\n"); |
| |
| register_syscore_ops(&imx8_mu_syscore_ops); |
| |
| pr_info("*****Initialized MU\n"); |
| return scu_mu_id; |
| } |
| |
| early_initcall(imx8_mu_init); |